├── .dir-locals.el ├── .gitattributes ├── .gitignore ├── META.json ├── Makefile ├── README.md ├── img ├── Postgres.svg ├── Redis.png └── experimental.png ├── redis_fdw--1.0.sql ├── redis_fdw.c ├── redis_fdw.control └── test ├── .gitignore ├── expected └── redis_fdw.out └── sql ├── redis_clean ├── redis_fdw.sql └── redis_setup /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;; see also src/tools/editors/emacs.samples for more complete settings 2 | 3 | ((c-mode . ((c-basic-offset . 4) 4 | (c-file-style . "bsd") 5 | (fill-column . 78) 6 | (indent-tabs-mode . t) 7 | (tab-width . 4))) 8 | (dsssl-mode . ((indent-tabs-mode . nil))) 9 | (nxml-mode . ((indent-tabs-mode . nil))) 10 | (perl-mode . ((perl-indent-level . 4) 11 | (perl-continued-statement-offset . 4) 12 | (perl-continued-brace-offset . 4) 13 | (perl-brace-offset . 0) 14 | (perl-brace-imaginary-offset . 0) 15 | (perl-label-offset . -2) 16 | (indent-tabs-mode . t) 17 | (tab-width . 4))) 18 | (sgml-mode . ((fill-column . 78) 19 | (indent-tabs-mode . nil)))) 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * whitespace=space-before-tab,trailing-space 2 | *.[chly] whitespace=space-before-tab,trailing-space,indent-with-non-tab,tabwidth=4 3 | *.out -whitespace 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Global excludes across all subdirectories 2 | *.o 3 | *.obj 4 | *.so 5 | *.so.[0-9] 6 | *.so.[0-9].[0-9] 7 | *.sl 8 | *.sl.[0-9] 9 | *.sl.[0-9].[0-9] 10 | *.dylib 11 | *.dll 12 | *.a 13 | *.mo 14 | *.pot 15 | .deps/ 16 | *.gcno 17 | *.gcda 18 | *.gcov 19 | *.gcov.out 20 | lcov.info 21 | coverage/ 22 | *.vcproj 23 | *.vcxproj 24 | win32ver.rc 25 | *.exe 26 | lib*dll.def 27 | lib*.pc 28 | 29 | # Local excludes in root directory 30 | /Debug/ 31 | /Release/ 32 | /tmp_install/ 33 | 34 | # Test artefacts 35 | /test/results/ 36 | /test/regression.diffs 37 | /test/regression.out 38 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redis_fdw", 3 | "abstract": "Redis FDW for PostgreSQL 9.1+", 4 | "description": "This extension implements a Foreign Data Wrapper for Redis. It is supported on PostgreSQL 9.1 and above.", 5 | "version": "1.0.0", 6 | "maintainer": [ 7 | "Dave Page " 8 | ], 9 | "license": "postgresql", 10 | "provides": { 11 | "redis_fdw": { 12 | "abstract": "Redis FDW for PostgreSQL 9.1+", 13 | "file": "redis_fdw--1.0.sql", 14 | "docfile": "README", 15 | "version": "1.0.0" 16 | } 17 | }, 18 | "prereqs": { 19 | "runtime": { 20 | "requires": { 21 | "PostgreSQL": "9.1.0" 22 | } 23 | } 24 | }, 25 | "resources": { 26 | "bugtracker": { 27 | "web": "https://github.com/dpage/redis_fdw/issues" 28 | }, 29 | "repository": { 30 | "url": "git://github.com/dpage/redis_fdw.git", 31 | "web": "http://github.com/dpage/redis_fdw/", 32 | "type": "git" 33 | } 34 | }, 35 | "generated_by": "Dave Page", 36 | "meta-spec": { 37 | "version": "1.0.0", 38 | "url": "http://pgxn.org/meta/spec.txt" 39 | }, 40 | "tags": [ 41 | "fdw", 42 | "redis", 43 | "foreign data wrapper" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ########################################################################## 2 | # 3 | # foreign-data wrapper for Redis 4 | # 5 | # Copyright (c) 2011,2013 PostgreSQL Global Development Group 6 | # 7 | # This software is released under the PostgreSQL Licence 8 | # 9 | # Authors: Dave Page 10 | # Andrew Dunstan 11 | # 12 | # IDENTIFICATION 13 | # redis_fdw/Makefile 14 | # 15 | ########################################################################## 16 | 17 | MODULE_big = redis_fdw 18 | OBJS = redis_fdw.o 19 | 20 | EXTENSION = redis_fdw 21 | DATA = redis_fdw--1.0.sql 22 | 23 | REGRESS = redis_fdw 24 | REGRESS_OPTS = --inputdir=test --outputdir=test \ 25 | --load-extension=hstore \ 26 | --load-extension=$(EXTENSION) 27 | 28 | EXTRA_CLEAN = sql/redis_fdw.sql expected/redis_fdw.out 29 | 30 | SHLIB_LINK += -lhiredis 31 | 32 | USE_PGXS = 1 33 | 34 | ifeq ($(USE_PGXS),1) 35 | PG_CONFIG = pg_config 36 | PGXS := $(shell $(PG_CONFIG) --pgxs) 37 | include $(PGXS) 38 | else 39 | subdir = contrib/redis_fdw 40 | top_builddir = ../.. 41 | include $(top_builddir)/src/Makefile.global 42 | include $(top_srcdir)/contrib/contrib-global.mk 43 | endif 44 | 45 | # we put all the tests in a test subdir, but pgxs expects us not to, darn it 46 | override pg_regress_clean_files = test/results/ test/regression.diffs test/regression.out tmp_check/ log/ 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Redis Foreign Data Wrapper for PostgreSQL 2 | ========================================== 3 | 4 | This is a foreign data wrapper (FDW) to connect [PostgreSQL](https://www.postgresql.org/) 5 | to [Redis](http://redis.io/) key/value databases. This FDW works with PostgreSQL 10+ 6 | and confirmed with some Redis versions near 6.0. 7 | 8 | PostgreSQL + Redis 9 | 10 | This code was originally experimental, and largely intended as a pet project 11 | for [Dave](#license-and-authors) to experiment with and learn about FDWs in PostgreSQL. 12 | It has now been extended for production use by [Andrew](#license-and-authors). 13 | 14 | ![image](img/experimental.png) 15 | 16 | **By all means use it, but do so entirely at your own risk!** You have been 17 | warned! 18 | 19 | Contents 20 | -------- 21 | 22 | 1. [Features](#features) 23 | 2. [Supported platforms](#supported-platforms) 24 | 3. [Installation](#installation) 25 | 4. [Usage](#usage) 26 | 5. [Functions](#functions) 27 | 6. [Identifier case handling](#identifier-case-handling) 28 | 7. [Generated columns](#generated-columns) 29 | 8. [Character set handling](#character-set-handling) 30 | 9. [Examples](#examples) 31 | 10. [Limitations](#limitations) 32 | 11. [Tests](#tests) 33 | 12. [Contributing](#contributing) 34 | 13. [Useful links](#useful-links) 35 | 14. [License and authors](#license-and-authors) 36 | 37 | Features 38 | -------- 39 | 40 | ### Common features 41 | - `SELECT` 42 | - `INSERT`, `UPDATE`, `DELETE`. There are a few restrictions for the operations: 43 | - only `INSERT` works for singleton key list tables, due to limitations 44 | in the Redis API for lists. 45 | - `INSERT` and `UPDATE` only work for singleton key `ZSET` tables if they have the 46 | priority column 47 | - non-singleton non-scalar tables must have an array type for the second column 48 | 49 | ### Pushdowning 50 | 51 | Not supported, there is no common calculations in Redis. 52 | 53 | ### Notes about features 54 | 55 | Also see [Limitations](#limitations) 56 | 57 | Supported platforms 58 | ------------------- 59 | 60 | `redis_fdw` was developed on Linux and Mac OS X and should run on any 61 | reasonably POSIX-compliant system. [Dave](#license-and-authors) has tested the 62 | original on Mac OS X 10.6 only, and [Andrew](#license-and-authors) on Fedora 63 | and Suse. Other *nix's should also work. Neither of us have tested on Windows, 64 | but the code should be good on MinGW. 65 | 66 | Installation 67 | ------------ 68 | 69 | ### Package installation 70 | 71 | No deb or rpm packages are available. 72 | 73 | ### Source installation 74 | 75 | #### Prerequisites: 76 | - A Redis database accessible from PostgreSQL server. 77 | - Local Redis *only* if you need `redis_fdw` testing. 78 | - [Hiredis C interface](https://github.com/redis/hiredis) installed 79 | on your system. You can checkout the `hiredis` from github or it might be available in [rpm or deb packages for your OS](https://pkgs.org/search/?q=hiredis). 80 | - PostgreSQL development package. For Debian or Ubuntu: `apt-get install postgresql-server-dev-XX -y`, where `XX` matches your postgres version, i.e. `apt-get install postgresql-server-dev-15 -y` 81 | 82 | #### Build and install on OS 83 | 84 | Ensure `pg_config` is callable without full path, build and install `regis_fdw` 85 | with commands below. Use release you need instead of `{REL}`, for ex. 86 | `REL_15_STABLE`, `REL_16_STABLE`. 87 | 88 | ```sh 89 | git clone https://github.com/pg-redis-fdw/redis_fdw.git -b {REL} 90 | 91 | make USE_PGXS=1 92 | sudo make install USE_PGXS=1 93 | ``` 94 | 95 | Make necessary changes for your PostgreSQL version if needed. 96 | You will need to have the right branch checked out to match the PostgreSQL 97 | release you are building against, as the FDW API has changed from release 98 | to release. 99 | 100 | Usage 101 | ----- 102 | 103 | ## CREATE SERVER options 104 | 105 | `redis_fdw` accepts the following options via the `CREATE SERVER` command: 106 | 107 | - **address** as *string*, optional, default `127.0.0.1` 108 | 109 | The address or hostname of the Redis server. 110 | 111 | - **port** as *integer*, optional, default `6379` 112 | 113 | The port number on which the Redis server is listening. 114 | 115 | ## CREATE USER MAPPING options 116 | 117 | `redis_fdw` accepts the following options via the `CREATE USER MAPPING` 118 | command: 119 | 120 | - **password** as *string*, no default 121 | 122 | The password to authenticate to the Redis server with. 123 | 124 | ## CREATE FOREIGN TABLE options 125 | 126 | `redis_fdw` accepts the following table-level options via the 127 | `CREATE FOREIGN TABLE` command: 128 | 129 | - **database** as *integer*, optional, default `0` 130 | 131 | The numeric ID of the Redis database to query. 132 | 133 | - **tabletype** as *string*, optional, no default 134 | 135 | Can be `hash`, `list`, `set` or `zset`. If not provided only look at scalar values. 136 | 137 | - **tablekeyprefix** as *string*, optional, no default 138 | 139 | Only get items whose names start with the prefix. 140 | 141 | - **tablekeyset** as *string*, optional, no default 142 | 143 | Fetch item names from the named set. In a Redis database with many keys, 144 | searching even using `tablekeyprefix` might still be expensive. In that case, 145 | you can keep a list of specific keys in a separate set and define it using 146 | `tablekeyset`. This way the global keyspace isn't searched at all. 147 | Only the keys in the `tablekeyset` will be mapped in the foreign table. 148 | 149 | - **singleton_key** as *string*, optional, no default 150 | 151 | Get all the values in the table from a single named object. If not provided 152 | don't use a single object. 153 | 154 | You can only have one of `tablekeyset` and `tablekeyprefix`, and if you use 155 | `singleton_key` you can't have either. 156 | 157 | Structured items are returned as `array text`, or, if the value column is a 158 | text array as an array of values. In the case of hash objects this array is 159 | an array of key, value, key, value ... 160 | 161 | Singleton key tables are returned as rows with a single column of text 162 | in the case of lists sets and scalars, rows with key and value text columns 163 | for hashes, and rows with a value text columns and an optional numeric score 164 | column for zsets. 165 | 166 | ## IMPORT FOREIGN SCHEMA options 167 | 168 | `redis_fdw` **doesn't support** [IMPORT FOREIGN SCHEMA](https://www.postgresql.org/docs/current/sql-importforeignschema.html) and accepts no custom options for this command. 169 | There is no formal storing schema in Redis in oppose to RDBMS. 170 | 171 | ## TRUNCATE support 172 | 173 | `redis_fdw` doesn't implements the foreign data wrapper `TRUNCATE` API, available 174 | from PostgreSQL 14. 175 | 176 | Functions 177 | --------- 178 | 179 | As well as the standard `redis_fdw_handler()` and `redis_fdw_validator()` 180 | functions, `redis_fdw` provides no user-callable utility functions. 181 | 182 | Identifier case handling 183 | ------------------------ 184 | 185 | PostgreSQL folds identifiers to lower case by default, Redis is case sensetive by default. 186 | It's important to be aware of potential issues with table and column names. 187 | If there will no proper name quoting in PostgreSQL, access from PostgreSQL foreign tables 188 | with mixedcase or uppercase names to mixedcase or uppercase Redis objects can cause 189 | unexpected results. 190 | 191 | Generated columns 192 | ----------------- 193 | 194 | Redis doesn't provide support for generated columns. 195 | 196 | For more details on generated columns see: 197 | 198 | - [Generated Columns](https://www.postgresql.org/docs/current/ddl-generated-columns.html) 199 | - [CREATE FOREIGN TABLE](https://www.postgresql.org/docs/current/sql-createforeigntable.html) 200 | 201 | Character set handling 202 | ---------------------- 203 | 204 | All strings from Redis are interpreted acording to the PostgreSQL database's server encoding. 205 | Redis supports UTF-8 only data. It's not a problem if the PostgreSQL server encoding is UTF-8. 206 | Behaviour with non-UTF8 PostgreSQL servers is undefined and untested. 207 | It is not recommended to use `redis_fdw` with non UTF-8 PostgreSQL databases. 208 | 209 | Examples 210 | -------- 211 | 212 | ### Install the extension: 213 | 214 | Once for a database you need, as PostgreSQL superuser. 215 | 216 | ```sql 217 | CREATE EXTENSION redis_fdw; 218 | ``` 219 | 220 | ### Create a foreign server with appropriate configuration: 221 | 222 | Once for a foreign datasource you need, as PostgreSQL superuser. 223 | 224 | ```sql 225 | CREATE SERVER redis_server 226 | FOREIGN DATA WRAPPER redis_fdw 227 | OPTIONS ( 228 | address '127.0.0.1', 229 | port '6379' 230 | ); 231 | ``` 232 | 233 | ### Grant usage on foreign server to normal user in PostgreSQL: 234 | 235 | Once for a normal user (non-superuser) in PostgreSQL, as PostgreSQL superuser. It is a good idea to use a superuser only where really necessary, so let's allow a normal user to use the foreign server (this is not required for the example to work, but it's secirity recomedation). 236 | 237 | ```sql 238 | GRANT USAGE ON FOREIGN SERVER redis_server TO pguser; 239 | ``` 240 | Where `pguser` is a sample user for works with foreign server (and foreign tables). 241 | 242 | ### User mapping 243 | 244 | ```sql 245 | CREATE USER MAPPING FOR pguser 246 | SERVER redis_server 247 | OPTIONS ( 248 | password 'secret' 249 | ); 250 | ``` 251 | Where `pguser` is a sample user for works with foreign server (and foreign tables). 252 | 253 | ### Create foreign table 254 | All `CREATE FOREIGN TABLE` SQL commands can be executed as a normal PostgreSQL user if there were correct `GRANT USAGE ON FOREIGN SERVER`. No need PostgreSQL supersuer for secirity reasons but also works with PostgreSQL supersuer. 255 | 256 | #### Simple table 257 | 258 | ```sql 259 | CREATE FOREIGN TABLE redis_db0 ( 260 | key text, 261 | val text 262 | ) 263 | SERVER redis_server 264 | OPTIONS ( 265 | database '0' 266 | ); 267 | ``` 268 | 269 | #### Hash table + `tablekeyprefix` 270 | 271 | ```sql 272 | CREATE FOREIGN TABLE myredishash ( 273 | key text, 274 | val text[] 275 | ) 276 | SERVER redis_server 277 | OPTIONS ( 278 | database '0', 279 | tabletype 'hash', 280 | tablekeyprefix 'mytable:' 281 | ); 282 | 283 | INSERT INTO myredishash (key, val) 284 | VALUES ('mytable:r1','{prop1,val1,prop2,val2}'); 285 | 286 | UPDATE myredishash 287 | SET val = '{prop3,val3,prop4,val4}' 288 | WHERE key = 'mytable:r1'; 289 | 290 | DELETE from myredishash 291 | WHERE key = 'mytable:r1'; 292 | ``` 293 | #### Hash table + `singleton_key` 294 | ```sql 295 | CREATE FOREIGN TABLE myredis_s_hash ( 296 | key text, 297 | val text 298 | ) 299 | SERVER redis_server 300 | OPTIONS ( 301 | database '0', 302 | tabletype 'hash', 303 | singleton_key 'mytable' 304 | ); 305 | 306 | INSERT INTO myredis_s_hash (key, val) 307 | VALUES ('prop1','val1'),('prop2','val2'); 308 | 309 | UPDATE myredis_s_hash 310 | SET val = 'val23' 311 | WHERE key = 'prop1'; 312 | 313 | DELETE from myredis_s_hash 314 | WHERE key = 'prop2'; 315 | ``` 316 | 317 | Limitations 318 | ----------- 319 | 320 | ### SQL commands 321 | - `COPY` command for foreign tables is not supported. 322 | - `TRUNCATE` is not supported. 323 | - `RETURNING` is not supported. 324 | 325 | ### Other 326 | - Redis has acquired cursors in 2.8+. This is used in all the 327 | mainline branches from REL9_2_STABLE on, for operations which would otherwise 328 | either scan the entire Redis database in a single sweep, or scan a single, 329 | possible large, keyset in a single sweep. 330 | 331 | - There is no [MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control), 332 | which leaves us with no way to atomically query the database for the available 333 | keys and then fetch each value. So, we get a list of keys to begin with, 334 | and then fetch whatever records still exist as we build the tuples. 335 | 336 | - We can only push down a single qual to Redis, which must use the 337 | `TEXTEQ` operator, and must be on the `key` column. 338 | 339 | - Redis cursors have some significant limitations. The Redis docs say: 340 | 341 | *A given element may be returned multiple times. It is up to the 342 | application to handle the case of duplicated elements, for example only 343 | using the returned elements in order to perform operations that are safe 344 | when re-applied multiple times*. 345 | 346 | The FDW makes no attempt to detect this situation. Users should be aware of 347 | the possibility. 348 | 349 | - There was no such thing as a cursor in Redis 2.8- in the SQL sense. Redis 350 | releases prior to 2.8 are maintained on the REL9_x_STABLE_pre2.8 branches. 351 | 352 | Tests 353 | ----- 354 | 355 | The tests for PostgreSQL assume that you have access to a Redis server 356 | on the local machine with no password, and uses PostgreSQL 15 server with 357 | *english* locale. This database must be empty, and that the `redis-cli` program 358 | is in the `PATH` envireonment variable when tests is run. 359 | The [test](test) script checks that the database is empty before it tries to 360 | populate it, and it cleans up afterwards. 361 | 362 | Some tests as `psql` expected outputs can be found in [test/expected](test/expected) directory. 363 | 364 | Contributing 365 | ------------ 366 | 367 | Opening issues and pull requests on GitHub are welcome. 368 | 369 | Useful links 370 | ------------ 371 | 372 | ### Redis selected documentation 373 | 374 | - https://redis.io/commands/ 375 | - https://redis.io/docs/ 376 | - https://redis.io/docs/data-types/ 377 | - https://github.com/redis/hiredis/blob/master/README.md 378 | 379 | ### Source code 380 | 381 | - https://github.com/redis/hiredis - hiredis C client library 382 | - https://github.com/redis/redis - redis DB 383 | - https://bitbucket.org/adunstan/redis_wrapper/src/master/ - PostgreSQL extension (not FDW) for Redis (also written by Andrew Dunstan) 384 | - https://github.com/jeffreydwalter/redis_cluster_fdw - Other FDW for Redis 385 | 386 | Reference FDW implementation, `postgres_fdw` 387 | - https://git.postgresql.org/gitweb/?p=postgresql.git;a=tree;f=contrib/postgres_fdw;hb=HEAD 388 | 389 | ### General FDW Documentation 390 | 391 | - https://www.postgresql.org/docs/current/ddl-foreign-data.html 392 | - https://www.postgresql.org/docs/current/sql-createforeigndatawrapper.html 393 | - https://www.postgresql.org/docs/current/sql-createforeigntable.html 394 | - https://www.postgresql.org/docs/current/sql-importforeignschema.html 395 | - https://www.postgresql.org/docs/current/fdwhandler.html 396 | - https://www.postgresql.org/docs/current/postgres-fdw.html 397 | 398 | ### Other FDWs 399 | 400 | - https://wiki.postgresql.org/wiki/Fdw 401 | - https://pgxn.org/tag/fdw/ 402 | 403 | License and authors 404 | ------- 405 | * Dave Page dpage@pgadmin.org 406 | * Andrew Dunstan andrew@dunslane.net 407 | -------------------------------------------------------------------------------- /img/Postgres.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /img/Redis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pg-redis-fdw/redis_fdw/12c933c00837586dc67a6bedc6a908faa088c2d3/img/Redis.png -------------------------------------------------------------------------------- /img/experimental.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pg-redis-fdw/redis_fdw/12c933c00837586dc67a6bedc6a908faa088c2d3/img/experimental.png -------------------------------------------------------------------------------- /redis_fdw--1.0.sql: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * foreign-data wrapper for Redis 4 | * 5 | * Copyright (c) 2011, PostgreSQL Global Development Group 6 | * 7 | * This software is released under the PostgreSQL Licence 8 | * 9 | * Author: Dave Page 10 | * 11 | * IDENTIFICATION 12 | * redis_fdw/redis_fdw--1.0.sql 13 | * 14 | *------------------------------------------------------------------------- 15 | */ 16 | 17 | CREATE FUNCTION redis_fdw_handler() 18 | RETURNS fdw_handler 19 | AS 'MODULE_PATHNAME' 20 | LANGUAGE C STRICT; 21 | 22 | CREATE FUNCTION redis_fdw_validator(text[], oid) 23 | RETURNS void 24 | AS 'MODULE_PATHNAME' 25 | LANGUAGE C STRICT; 26 | 27 | CREATE FOREIGN DATA WRAPPER redis_fdw 28 | HANDLER redis_fdw_handler 29 | VALIDATOR redis_fdw_validator; 30 | -------------------------------------------------------------------------------- /redis_fdw.c: -------------------------------------------------------------------------------- 1 | 2 | /*------------------------------------------------------------------------- 3 | * 4 | * foreign-data wrapper for Redis 5 | * 6 | * Copyright (c) 2011,2013 PostgreSQL Global Development Group 7 | * 8 | * This software is released under the PostgreSQL Licence 9 | * 10 | * Authors: Dave Page 11 | * Andrew Dunstan 12 | * 13 | * IDENTIFICATION 14 | * redis_fdw/redis_fdw.c 15 | * 16 | *------------------------------------------------------------------------- 17 | */ 18 | 19 | /* Debug mode */ 20 | /* #define DEBUG */ 21 | 22 | #include "postgres.h" 23 | 24 | /* check that we are compiling for the right postgres version */ 25 | #if PG_VERSION_NUM < 180000 26 | #error wrong Postgresql version this branch is only for 18. 27 | #endif 28 | 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | #include "funcapi.h" 37 | #include "access/reloptions.h" 38 | #include "access/sysattr.h" 39 | #include "access/table.h" 40 | #include "catalog/pg_foreign_server.h" 41 | #include "catalog/pg_foreign_table.h" 42 | #include "catalog/pg_user_mapping.h" 43 | #include "catalog/pg_type.h" 44 | #include "commands/defrem.h" 45 | #include "commands/explain_format.h" 46 | #include "commands/explain_state.h" 47 | #include "foreign/fdwapi.h" 48 | #include "foreign/foreign.h" 49 | #include "miscadmin.h" 50 | #include "mb/pg_wchar.h" 51 | #include "nodes/pathnodes.h" 52 | #include "nodes/makefuncs.h" 53 | #include "nodes/parsenodes.h" 54 | #include "optimizer/appendinfo.h" 55 | #include "optimizer/inherit.h" 56 | #include "optimizer/optimizer.h" 57 | #include "optimizer/pathnode.h" 58 | #include "optimizer/planmain.h" 59 | #include "optimizer/restrictinfo.h" 60 | #include "parser/parsetree.h" 61 | #include "storage/fd.h" 62 | #include "utils/array.h" 63 | #include "utils/builtins.h" 64 | #include "utils/lsyscache.h" 65 | #include "utils/rel.h" 66 | 67 | PG_MODULE_MAGIC; 68 | 69 | #define PROCID_TEXTEQ 67 70 | 71 | /* 72 | * Describes the valid options for objects that use this wrapper. 73 | */ 74 | struct RedisFdwOption 75 | { 76 | const char *optname; 77 | Oid optcontext; /* Oid of catalog in which option may appear */ 78 | }; 79 | 80 | /* 81 | * Valid options for redis_fdw. 82 | * 83 | */ 84 | static struct RedisFdwOption valid_options[] = 85 | { 86 | 87 | /* Connection options */ 88 | {"address", ForeignServerRelationId}, 89 | {"port", ForeignServerRelationId}, 90 | {"password", UserMappingRelationId}, 91 | 92 | /* table options */ 93 | {"database", ForeignTableRelationId}, 94 | {"singleton_key", ForeignTableRelationId}, 95 | {"tablekeyprefix", ForeignTableRelationId}, 96 | {"tablekeyset", ForeignTableRelationId}, 97 | {"tabletype", ForeignTableRelationId}, 98 | 99 | /* Sentinel */ 100 | {NULL, InvalidOid} 101 | }; 102 | 103 | typedef enum 104 | { 105 | PG_REDIS_SCALAR_TABLE = 0, 106 | PG_REDIS_HASH_TABLE, 107 | PG_REDIS_LIST_TABLE, 108 | PG_REDIS_SET_TABLE, 109 | PG_REDIS_ZSET_TABLE 110 | } redis_table_type; 111 | 112 | typedef struct redisTableOptions 113 | { 114 | char *address; 115 | int port; 116 | char *password; 117 | int database; 118 | char *keyprefix; 119 | char *keyset; 120 | char *singleton_key; 121 | redis_table_type table_type; 122 | } redisTableOptions; 123 | 124 | typedef struct 125 | { 126 | char *svr_address; 127 | int svr_port; 128 | char *svr_password; 129 | int svr_database; 130 | } RedisFdwPlanState; 131 | 132 | /* 133 | * FDW-specific information for ForeignScanState.fdw_state. 134 | */ 135 | 136 | typedef struct RedisFdwExecutionState 137 | { 138 | AttInMetadata *attinmeta; 139 | redisContext *context; 140 | redisReply *reply; 141 | long long row; 142 | char *address; 143 | int port; 144 | char *password; 145 | int database; 146 | char *keyprefix; 147 | char *keyset; 148 | char *qual_value; 149 | char *singleton_key; 150 | redis_table_type table_type; 151 | char *cursor_search_string; 152 | char *cursor_id; 153 | MemoryContext mctxt; 154 | } RedisFdwExecutionState; 155 | 156 | typedef struct RedisFdwModifyState 157 | { 158 | redisContext *context; 159 | char *address; 160 | int port; 161 | char *password; 162 | int database; 163 | char *keyprefix; 164 | char *keyset; 165 | char *qual_value; 166 | char *singleton_key; 167 | Relation rel; 168 | redis_table_type table_type; 169 | List *target_attrs; 170 | int *targetDims; 171 | int p_nums; 172 | int keyAttno; 173 | Oid array_elem_type; 174 | FmgrInfo *p_flinfo; 175 | } RedisFdwModifyState; 176 | 177 | /* initial cursor */ 178 | #define ZERO "0" 179 | /* redis default is 10 - let's fetch 1000 at a time */ 180 | #define COUNT " COUNT 1000" 181 | 182 | /* 183 | * SQL functions 184 | */ 185 | extern Datum redis_fdw_handler(PG_FUNCTION_ARGS); 186 | extern Datum redis_fdw_validator(PG_FUNCTION_ARGS); 187 | 188 | PG_FUNCTION_INFO_V1(redis_fdw_handler); 189 | PG_FUNCTION_INFO_V1(redis_fdw_validator); 190 | 191 | /* 192 | * FDW callback routines 193 | */ 194 | static void redisGetForeignRelSize(PlannerInfo *root, 195 | RelOptInfo *baserel, 196 | Oid foreigntableid); 197 | static void redisGetForeignPaths(PlannerInfo *root, 198 | RelOptInfo *baserel, 199 | Oid foreigntableid); 200 | static ForeignScan *redisGetForeignPlan(PlannerInfo *root, 201 | RelOptInfo *baserel, 202 | Oid foreigntableid, 203 | ForeignPath *best_path, 204 | List *tlist, 205 | List *scan_clauses, 206 | Plan *outer_plan); 207 | 208 | static void redisExplainForeignScan(ForeignScanState *node, ExplainState *es); 209 | static void redisBeginForeignScan(ForeignScanState *node, int eflags); 210 | static TupleTableSlot *redisIterateForeignScan(ForeignScanState *node); 211 | static inline TupleTableSlot *redisIterateForeignScanMulti(ForeignScanState *node); 212 | static inline TupleTableSlot *redisIterateForeignScanSingleton(ForeignScanState *node); 213 | static void redisReScanForeignScan(ForeignScanState *node); 214 | static void redisEndForeignScan(ForeignScanState *node); 215 | 216 | 217 | static List *redisPlanForeignModify(PlannerInfo *root, 218 | ModifyTable *plan, 219 | Index resultRelation, 220 | int subplan_index); 221 | 222 | static void redisBeginForeignModify(ModifyTableState *mtstate, 223 | ResultRelInfo *rinfo, 224 | List *fdw_private, 225 | int subplan_index, 226 | int eflags); 227 | 228 | static TupleTableSlot *redisExecForeignInsert(EState *estate, 229 | ResultRelInfo *rinfo, 230 | TupleTableSlot *slot, 231 | TupleTableSlot *planSlot); 232 | static void redisEndForeignModify(EState *estate, 233 | ResultRelInfo *rinfo); 234 | static void redisAddForeignUpdateTargets(PlannerInfo *root, 235 | Index rtindex, 236 | RangeTblEntry *target_rte, 237 | Relation target_relation); 238 | static TupleTableSlot *redisExecForeignDelete(EState *estate, 239 | ResultRelInfo *rinfo, 240 | TupleTableSlot *slot, 241 | TupleTableSlot *planSlot); 242 | static TupleTableSlot *redisExecForeignUpdate(EState *estate, 243 | ResultRelInfo *rinfo, 244 | TupleTableSlot *slot, 245 | TupleTableSlot *planSlot); 246 | 247 | /* 248 | * Helper functions 249 | */ 250 | static bool redisIsValidOption(const char *option, Oid context); 251 | static void redisGetOptions(Oid foreigntableid, redisTableOptions *options); 252 | static void redisGetQual(Node *node, TupleDesc tupdesc, char **key, 253 | char **value, bool *pushdown); 254 | static char *process_redis_array(redisReply *reply, redis_table_type type); 255 | static void check_reply(redisReply *reply, redisContext *context, 256 | int error_code, char *message, char *arg); 257 | 258 | /* 259 | * Name we will use for the junk attribute that holds the redis key 260 | * for update and delete operations. 261 | */ 262 | #define REDISMODKEYNAME "__redis_mod_key_name" 263 | 264 | /* 265 | * redis_fdw_handler 266 | * Foreign-data wrapper handler function: return a struct with pointers 267 | * to my callback routines. 268 | */ 269 | Datum 270 | redis_fdw_handler(PG_FUNCTION_ARGS) 271 | { 272 | FdwRoutine *fdwroutine = makeNode(FdwRoutine); 273 | 274 | #ifdef DEBUG 275 | elog(NOTICE, "redis_fdw_handler"); 276 | #endif 277 | 278 | fdwroutine->GetForeignRelSize = redisGetForeignRelSize; 279 | fdwroutine->GetForeignPaths = redisGetForeignPaths; 280 | fdwroutine->GetForeignPlan = redisGetForeignPlan; 281 | /* can't ANALYSE redis */ 282 | fdwroutine->AnalyzeForeignTable = NULL; 283 | fdwroutine->ExplainForeignScan = redisExplainForeignScan; 284 | fdwroutine->BeginForeignScan = redisBeginForeignScan; 285 | fdwroutine->IterateForeignScan = redisIterateForeignScan; 286 | fdwroutine->ReScanForeignScan = redisReScanForeignScan; 287 | fdwroutine->EndForeignScan = redisEndForeignScan; 288 | 289 | fdwroutine->PlanForeignModify = redisPlanForeignModify; /* I U D */ 290 | fdwroutine->BeginForeignModify = redisBeginForeignModify; /* I U D */ 291 | fdwroutine->ExecForeignInsert = redisExecForeignInsert; /* I */ 292 | fdwroutine->EndForeignModify = redisEndForeignModify; /* I U D */ 293 | 294 | fdwroutine->ExecForeignUpdate = redisExecForeignUpdate; /* U */ 295 | fdwroutine->ExecForeignDelete = redisExecForeignDelete; /* D */ 296 | fdwroutine->AddForeignUpdateTargets = redisAddForeignUpdateTargets; /* U D */ 297 | 298 | PG_RETURN_POINTER(fdwroutine); 299 | } 300 | 301 | /* 302 | * redis_fdw_validator 303 | * Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER, 304 | * USER MAPPING or FOREIGN TABLE that uses file_fdw. 305 | * 306 | * Raise an ERROR if the option or its value is considered invalid. 307 | */ 308 | Datum 309 | redis_fdw_validator(PG_FUNCTION_ARGS) 310 | { 311 | List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); 312 | Oid catalog = PG_GETARG_OID(1); 313 | char *svr_address = NULL; 314 | int svr_port = 0; 315 | char *svr_password = NULL; 316 | int svr_database = 0; 317 | redis_table_type tabletype = PG_REDIS_SCALAR_TABLE; 318 | char *tablekeyprefix = NULL; 319 | char *tablekeyset = NULL; 320 | char *singletonkey = NULL; 321 | ListCell *cell; 322 | 323 | #ifdef DEBUG 324 | elog(NOTICE, "redis_fdw_validator"); 325 | #endif 326 | 327 | /* 328 | * Check that only options supported by redis_fdw, and allowed for the 329 | * current object type, are given. 330 | */ 331 | foreach(cell, options_list) 332 | { 333 | DefElem *def = (DefElem *) lfirst(cell); 334 | 335 | if (!redisIsValidOption(def->defname, catalog)) 336 | { 337 | struct RedisFdwOption *opt; 338 | StringInfoData buf; 339 | 340 | /* 341 | * Unknown option specified, complain about it. Provide a hint 342 | * with list of valid options for the object. 343 | */ 344 | initStringInfo(&buf); 345 | for (opt = valid_options; opt->optname; opt++) 346 | { 347 | if (catalog == opt->optcontext) 348 | appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", 349 | opt->optname); 350 | } 351 | 352 | ereport(ERROR, 353 | (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), 354 | errmsg("invalid option \"%s\"", def->defname), 355 | errhint("Valid options in this context are: %s", 356 | buf.len ? buf.data : "") 357 | )); 358 | } 359 | 360 | if (strcmp(def->defname, "address") == 0) 361 | { 362 | if (svr_address) 363 | ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), 364 | errmsg("conflicting or redundant options: " 365 | "address (%s)", defGetString(def)) 366 | )); 367 | 368 | svr_address = defGetString(def); 369 | } 370 | else if (strcmp(def->defname, "port") == 0) 371 | { 372 | if (svr_port) 373 | ereport(ERROR, 374 | (errcode(ERRCODE_SYNTAX_ERROR), 375 | errmsg("conflicting or redundant options: port (%s)", 376 | defGetString(def)) 377 | )); 378 | 379 | svr_port = atoi(defGetString(def)); 380 | } 381 | if (strcmp(def->defname, "password") == 0) 382 | { 383 | if (svr_password) 384 | ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), 385 | errmsg("conflicting or redundant options: password") 386 | )); 387 | 388 | svr_password = defGetString(def); 389 | } 390 | else if (strcmp(def->defname, "database") == 0) 391 | { 392 | if (svr_database) 393 | ereport(ERROR, 394 | (errcode(ERRCODE_SYNTAX_ERROR), 395 | errmsg("conflicting or redundant options: database " 396 | "(%s)", defGetString(def)) 397 | )); 398 | 399 | svr_database = atoi(defGetString(def)); 400 | } 401 | else if (strcmp(def->defname, "singleton_key ") == 0) 402 | { 403 | if (tablekeyset) 404 | ereport(ERROR, 405 | (errcode(ERRCODE_SYNTAX_ERROR), 406 | errmsg("conflicting options: tablekeyset(%s) and " 407 | "singleton_key (%s)", tablekeyset, 408 | defGetString(def)) 409 | )); 410 | if (tablekeyprefix) 411 | ereport(ERROR, 412 | (errcode(ERRCODE_SYNTAX_ERROR), 413 | errmsg("conflicting options: tablekeyprefix(%s) and " 414 | "singleton_key (%s)", tablekeyprefix, 415 | defGetString(def)) 416 | )); 417 | if (singletonkey) 418 | ereport(ERROR, 419 | (errcode(ERRCODE_SYNTAX_ERROR), 420 | errmsg("conflicting or redundant options: " 421 | "singleton_key (%s)", defGetString(def)) 422 | )); 423 | 424 | singletonkey = defGetString(def); 425 | } 426 | else if (strcmp(def->defname, "tablekeyprefix") == 0) 427 | { 428 | if (tablekeyset) 429 | ereport(ERROR, 430 | (errcode(ERRCODE_SYNTAX_ERROR), 431 | errmsg("conflicting options: tablekeyset(%s) and " 432 | "tablekeyprefix (%s)", tablekeyset, 433 | defGetString(def)) 434 | )); 435 | if (singletonkey) 436 | ereport(ERROR, 437 | (errcode(ERRCODE_SYNTAX_ERROR), 438 | errmsg("conflicting options: singleton_key(%s) and " 439 | "tablekeyprefix (%s)", singletonkey, 440 | defGetString(def)) 441 | )); 442 | if (tablekeyprefix) 443 | ereport(ERROR, 444 | (errcode(ERRCODE_SYNTAX_ERROR), 445 | errmsg("conflicting or redundant options: " 446 | "tablekeyprefix (%s)", defGetString(def)) 447 | )); 448 | 449 | tablekeyprefix = defGetString(def); 450 | } 451 | else if (strcmp(def->defname, "tablekeyset") == 0) 452 | { 453 | if (tablekeyprefix) 454 | ereport(ERROR, 455 | (errcode(ERRCODE_SYNTAX_ERROR), 456 | errmsg("conflicting options: tablekeyprefix (%s) and " 457 | "tablekeyset (%s)", tablekeyprefix, 458 | defGetString(def)) 459 | )); 460 | if (singletonkey) 461 | ereport(ERROR, 462 | (errcode(ERRCODE_SYNTAX_ERROR), 463 | errmsg("conflicting options: singleton_key(%s) and " 464 | "tablekeyset (%s)", singletonkey, 465 | defGetString(def)) 466 | )); 467 | if (tablekeyset) 468 | ereport(ERROR, 469 | (errcode(ERRCODE_SYNTAX_ERROR), 470 | errmsg("conflicting or redundant options: " 471 | "tablekeyset (%s)", defGetString(def)) 472 | )); 473 | 474 | tablekeyset = defGetString(def); 475 | } 476 | else if (strcmp(def->defname, "tabletype") == 0) 477 | { 478 | char *typeval = defGetString(def); 479 | 480 | if (tabletype) 481 | ereport(ERROR, 482 | (errcode(ERRCODE_SYNTAX_ERROR), 483 | errmsg("conflicting or redundant options: tabletype " 484 | "(%s)", typeval))); 485 | if (strcmp(typeval, "hash") == 0) 486 | tabletype = PG_REDIS_HASH_TABLE; 487 | else if (strcmp(typeval, "list") == 0) 488 | tabletype = PG_REDIS_LIST_TABLE; 489 | else if (strcmp(typeval, "set") == 0) 490 | tabletype = PG_REDIS_SET_TABLE; 491 | else if (strcmp(typeval, "zset") == 0) 492 | tabletype = PG_REDIS_ZSET_TABLE; 493 | else 494 | ereport(ERROR, 495 | (errcode(ERRCODE_SYNTAX_ERROR), 496 | errmsg("invalid tabletype (%s) - must be hash, " 497 | "list, set or zset", typeval))); 498 | } 499 | } 500 | 501 | PG_RETURN_VOID(); 502 | } 503 | 504 | /* 505 | * redisIsValidOption 506 | * Check if the provided option is one of the valid options. 507 | * context is the Oid of the catalog holding the object the option is for. 508 | */ 509 | static bool 510 | redisIsValidOption(const char *option, Oid context) 511 | { 512 | struct RedisFdwOption *opt; 513 | 514 | #ifdef DEBUG 515 | elog(NOTICE, "redisIsValidOption"); 516 | #endif 517 | 518 | for (opt = valid_options; opt->optname; opt++) 519 | { 520 | if (context == opt->optcontext && strcmp(opt->optname, option) == 0) 521 | return true; 522 | } 523 | return false; 524 | } 525 | 526 | /* 527 | * redisGetOptions 528 | * Fetch the options for a redis_fdw foreign table. 529 | */ 530 | static void 531 | redisGetOptions(Oid foreigntableid, redisTableOptions *table_options) 532 | { 533 | ForeignTable *table; 534 | ForeignServer *server; 535 | UserMapping *mapping; 536 | List *options; 537 | ListCell *lc; 538 | 539 | #ifdef DEBUG 540 | elog(NOTICE, "redisGetOptions"); 541 | #endif 542 | 543 | /* Set void values */ 544 | table_options->address = NULL; 545 | table_options->port = 0; 546 | table_options->password = NULL; 547 | table_options->database = 0; 548 | table_options->keyprefix = NULL; 549 | table_options->keyset = NULL; 550 | table_options->singleton_key = NULL; 551 | table_options->table_type = PG_REDIS_SCALAR_TABLE; 552 | 553 | /* 554 | * Extract options from FDW objects. We only need to worry about server 555 | * options for Redis 556 | */ 557 | table = GetForeignTable(foreigntableid); 558 | server = GetForeignServer(table->serverid); 559 | mapping = GetUserMapping(GetUserId(), table->serverid); 560 | 561 | options = NIL; 562 | options = list_concat(options, table->options); 563 | options = list_concat(options, server->options); 564 | options = list_concat(options, mapping->options); 565 | 566 | /* Loop through the options, and get the server/port */ 567 | foreach(lc, options) 568 | { 569 | DefElem *def = (DefElem *) lfirst(lc); 570 | 571 | if (strcmp(def->defname, "address") == 0) 572 | table_options->address = defGetString(def); 573 | 574 | if (strcmp(def->defname, "port") == 0) 575 | table_options->port = atoi(defGetString(def)); 576 | 577 | if (strcmp(def->defname, "password") == 0) 578 | table_options->password = defGetString(def); 579 | 580 | if (strcmp(def->defname, "database") == 0) 581 | table_options->database = atoi(defGetString(def)); 582 | 583 | if (strcmp(def->defname, "tablekeyprefix") == 0) 584 | table_options->keyprefix = defGetString(def); 585 | 586 | if (strcmp(def->defname, "tablekeyset") == 0) 587 | table_options->keyset = defGetString(def); 588 | 589 | if (strcmp(def->defname, "singleton_key") == 0) 590 | table_options->singleton_key = defGetString(def); 591 | 592 | if (strcmp(def->defname, "tabletype") == 0) 593 | { 594 | char *typeval = defGetString(def); 595 | 596 | if (strcmp(typeval, "hash") == 0) 597 | table_options->table_type = PG_REDIS_HASH_TABLE; 598 | else if (strcmp(typeval, "list") == 0) 599 | table_options->table_type = PG_REDIS_LIST_TABLE; 600 | else if (strcmp(typeval, "set") == 0) 601 | table_options->table_type = PG_REDIS_SET_TABLE; 602 | else if (strcmp(typeval, "zset") == 0) 603 | table_options->table_type = PG_REDIS_ZSET_TABLE; 604 | else 605 | ereport(ERROR, 606 | (errcode(ERRCODE_SYNTAX_ERROR), 607 | errmsg("invalid tabletype (%s) - must be hash, " 608 | "list, set or zset", typeval))); 609 | } 610 | } 611 | 612 | /* Default values, if required */ 613 | if (!table_options->address) 614 | table_options->address = "127.0.0.1"; 615 | 616 | if (!table_options->port) 617 | table_options->port = 6379; 618 | 619 | if (!table_options->database) 620 | table_options->database = 0; 621 | } 622 | 623 | /* 624 | * redisGetForeignRelSize 625 | * Gets size of a foreign realtion as value from 626 | * HLEN, LLEN, SCARD, ZCARD or DBSIZE Redis command 627 | * returns baserel->rows 628 | */ 629 | static void 630 | redisGetForeignRelSize(PlannerInfo *root, 631 | RelOptInfo *baserel, 632 | Oid foreigntableid) 633 | { 634 | RedisFdwPlanState *fdw_private; 635 | redisTableOptions table_options; 636 | 637 | redisContext *context; 638 | redisReply *reply; 639 | struct timeval timeout = {1, 500000}; 640 | 641 | #ifdef DEBUG 642 | elog(NOTICE, "redisGetForeignRelSize"); 643 | #endif 644 | 645 | /* 646 | * Fetch options. Get everything so we don't need to re-fetch it later in 647 | * planning. 648 | */ 649 | fdw_private = (RedisFdwPlanState *) palloc(sizeof(RedisFdwPlanState)); 650 | baserel->fdw_private = (void *) fdw_private; 651 | 652 | redisGetOptions(foreigntableid, &table_options); 653 | fdw_private->svr_address = table_options.address; 654 | fdw_private->svr_password = table_options.password; 655 | fdw_private->svr_port = table_options.port; 656 | fdw_private->svr_database = table_options.database; 657 | 658 | /* Connect to the database */ 659 | context = redisConnectWithTimeout(table_options.address, table_options.port, 660 | timeout); 661 | if (context->err) 662 | ereport(ERROR, 663 | (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), 664 | errmsg("failed to connect to Redis: %d", context->err) 665 | )); 666 | 667 | /* Authenticate */ 668 | if (table_options.password) 669 | { 670 | reply = redisCommand(context, "AUTH %s", table_options.password); 671 | 672 | if (!reply) 673 | { 674 | redisFree(context); 675 | ereport(ERROR, 676 | (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), 677 | errmsg("failed to authenticate to redis: %d", 678 | context->err))); 679 | } 680 | 681 | freeReplyObject(reply); 682 | } 683 | 684 | /* Select the appropriate database */ 685 | reply = redisCommand(context, "SELECT %d", table_options.database); 686 | 687 | if (!reply) 688 | { 689 | redisFree(context); 690 | ereport(ERROR, 691 | (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), 692 | errmsg("failed to select database %d: %d", 693 | table_options.database, context->err) 694 | )); 695 | } 696 | 697 | /* Execute a query to get the table size */ 698 | #if 0 699 | 700 | /* 701 | * KEYS is potentially expensive, so this test is disabled and we use a 702 | * fairly dubious heuristic instead. 703 | */ 704 | if (table_options.keyprefix) 705 | { 706 | /* it's a pity there isn't an NKEYS command in Redis */ 707 | int len = strlen(table_options.keyprefix) + 2; 708 | char *buff = palloc(len * sizeof(char)); 709 | 710 | snprintf(buff, len, "%s*", table_options.keyprefix); 711 | reply = redisCommand(context, "KEYS %s", buff); 712 | } 713 | else 714 | #endif 715 | if (table_options.singleton_key) 716 | { 717 | switch (table_options.table_type) 718 | { 719 | case PG_REDIS_SCALAR_TABLE: 720 | baserel->rows = 1; 721 | return; 722 | case PG_REDIS_HASH_TABLE: 723 | reply = redisCommand(context, "HLEN %s", table_options.singleton_key); 724 | break; 725 | case PG_REDIS_LIST_TABLE: 726 | reply = redisCommand(context, "LLEN %s", table_options.singleton_key); 727 | break; 728 | case PG_REDIS_SET_TABLE: 729 | reply = redisCommand(context, "SCARD %s", table_options.singleton_key); 730 | break; 731 | case PG_REDIS_ZSET_TABLE: 732 | reply = redisCommand(context, "ZCARD %s", table_options.singleton_key); 733 | break; 734 | default: 735 | ; 736 | } 737 | } 738 | else if (table_options.keyset) 739 | { 740 | reply = redisCommand(context, "SCARD %s", table_options.keyset); 741 | } 742 | else 743 | { 744 | reply = redisCommand(context, "DBSIZE"); 745 | } 746 | 747 | if (!reply) 748 | { 749 | redisFree(context); 750 | ereport(ERROR, 751 | (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), 752 | errmsg("failed to get the database size: %d", context->err) 753 | )); 754 | } 755 | 756 | #if 0 757 | if (reply->type == REDIS_REPLY_ARRAY) 758 | baserel->rows = reply->elements; 759 | else 760 | #endif 761 | if (table_options.keyprefix) 762 | baserel->rows = reply->integer / 20; 763 | else 764 | baserel->rows = reply->integer; 765 | 766 | freeReplyObject(reply); 767 | redisFree(context); 768 | } 769 | 770 | /* 771 | * redisGetForeignPaths 772 | * Create possible access paths for a scan on the foreign table 773 | * 774 | * Currently we don't support any push-down feature, so there is only one 775 | * possible access path, which simply returns all records in redis. 776 | */ 777 | static void 778 | redisGetForeignPaths(PlannerInfo *root, 779 | RelOptInfo *baserel, 780 | Oid foreigntableid) 781 | { 782 | RedisFdwPlanState *fdw_private = baserel->fdw_private; 783 | 784 | Cost startup_cost, 785 | total_cost; 786 | 787 | #ifdef DEBUG 788 | elog(NOTICE, "redisGetForeignPaths"); 789 | #endif 790 | 791 | if (strcmp(fdw_private->svr_address, "127.0.0.1") == 0 || 792 | strcmp(fdw_private->svr_address, "localhost") == 0) 793 | startup_cost = 10; 794 | else 795 | startup_cost = 25; 796 | 797 | total_cost = startup_cost + baserel->rows; 798 | 799 | 800 | /* Create a ForeignPath node and add it as only possible path */ 801 | add_path(baserel, (Path *) 802 | create_foreignscan_path(root, baserel, 803 | NULL, /* default pathtarget */ 804 | baserel->rows, 805 | 0, /* no disabled nodes */ 806 | startup_cost, 807 | total_cost, 808 | NIL, /* no pathkeys */ 809 | NULL, /* no outer rel either */ 810 | NULL, /* no extra plan */ 811 | NIL, /* no fdw_restrictinfo list */ 812 | NIL)); /* no fdw_private data */ 813 | 814 | } 815 | 816 | /* 817 | * redisGetForeignPlan 818 | * Create ForeignScan plan node which implements only possible execution 819 | * "path" for Redis 820 | */ 821 | static ForeignScan * 822 | redisGetForeignPlan(PlannerInfo *root, 823 | RelOptInfo *baserel, 824 | Oid foreigntableid, 825 | ForeignPath *best_path, 826 | List *tlist, 827 | List *scan_clauses, 828 | Plan *outer_plan) 829 | { 830 | Index scan_relid = baserel->relid; 831 | 832 | #ifdef DEBUG 833 | elog(NOTICE, "redisGetForeignPlan"); 834 | #endif 835 | 836 | /* 837 | * We have no native ability to evaluate restriction clauses, so we just 838 | * put all the scan_clauses into the plan node's qual list for the 839 | * executor to check. So all we have to do here is strip RestrictInfo 840 | * nodes from the clauses and ignore pseudoconstants (which will be 841 | * handled elsewhere). 842 | */ 843 | scan_clauses = extract_actual_clauses(scan_clauses, false); 844 | 845 | /* Create the ForeignScan node */ 846 | return make_foreignscan(tlist, 847 | scan_clauses, 848 | scan_relid, 849 | NIL, /* no expressions to evaluate */ 850 | NIL, /* no private state either */ 851 | NIL, /* no custom tlist */ 852 | NIL, /* no remote quals */ 853 | outer_plan); 854 | } 855 | 856 | /* 857 | * fileExplainForeignScan 858 | * Produce extra output for EXPLAIN 859 | */ 860 | static void 861 | redisExplainForeignScan(ForeignScanState *node, ExplainState *es) 862 | { 863 | redisReply *reply; 864 | 865 | RedisFdwExecutionState *festate = (RedisFdwExecutionState *) node->fdw_state; 866 | 867 | #ifdef DEBUG 868 | elog(NOTICE, "redisExplainForeignScan"); 869 | #endif 870 | 871 | if (!es->costs) 872 | return; 873 | 874 | /* 875 | * Execute a query to get the table size 876 | * 877 | * See above for more details. 878 | */ 879 | 880 | if (festate->keyset) 881 | { 882 | reply = redisCommand(festate->context, "SCARD %s", festate->keyset); 883 | } 884 | else 885 | { 886 | reply = redisCommand(festate->context, "DBSIZE"); 887 | } 888 | 889 | if (!reply) 890 | { 891 | redisFree(festate->context); 892 | ereport(ERROR, 893 | (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), 894 | errmsg("failed to get the table size: %d", festate->context->err) 895 | )); 896 | } 897 | 898 | if (reply->type == REDIS_REPLY_ERROR) 899 | { 900 | char *err = pstrdup(reply->str); 901 | 902 | ereport(ERROR, 903 | (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), 904 | errmsg("failed to get the table size: %s", err) 905 | )); 906 | } 907 | 908 | ExplainPropertyInteger("Foreign Redis Table Size", "b", 909 | festate->keyprefix ? reply->integer / 20 : 910 | reply->integer, 911 | es); 912 | 913 | freeReplyObject(reply); 914 | } 915 | 916 | /* 917 | * redisBeginForeignScan 918 | * Initiate access to the database 919 | */ 920 | static void 921 | redisBeginForeignScan(ForeignScanState *node, int eflags) 922 | { 923 | redisTableOptions table_options; 924 | redisContext *context; 925 | redisReply *reply; 926 | char *qual_key = NULL; 927 | char *qual_value = NULL; 928 | bool pushdown = false; 929 | RedisFdwExecutionState *festate; 930 | struct timeval timeout = {1, 500000}; 931 | 932 | #ifdef DEBUG 933 | elog(NOTICE, "BeginForeignScan"); 934 | #endif 935 | 936 | /* Fetch options */ 937 | redisGetOptions(RelationGetRelid(node->ss.ss_currentRelation), 938 | &table_options); 939 | 940 | /* Connect to the server */ 941 | context = redisConnectWithTimeout(table_options.address, 942 | table_options.port, timeout); 943 | 944 | if (context->err) 945 | { 946 | redisFree(context); 947 | ereport(ERROR, 948 | (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), 949 | errmsg("failed to connect to Redis: %s", context->errstr) 950 | )); 951 | } 952 | 953 | /* Authenticate */ 954 | if (table_options.password) 955 | { 956 | reply = redisCommand(context, "AUTH %s", table_options.password); 957 | 958 | if (!reply) 959 | { 960 | redisFree(context); 961 | ereport(ERROR, 962 | (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), 963 | errmsg("failed to authenticate to redis: %s", context->errstr) 964 | )); 965 | } 966 | 967 | freeReplyObject(reply); 968 | } 969 | 970 | /* Select the appropriate database */ 971 | reply = redisCommand(context, "SELECT %d", table_options.database); 972 | 973 | if (!reply) 974 | { 975 | redisFree(context); 976 | ereport(ERROR, 977 | (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), 978 | errmsg("failed to select database %d: %s", 979 | table_options.database, context->errstr) 980 | )); 981 | } 982 | 983 | if (reply->type == REDIS_REPLY_ERROR) 984 | { 985 | char *err = pstrdup(reply->str); 986 | 987 | ereport(ERROR, 988 | (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), 989 | errmsg("failed to select database %d: %s", 990 | table_options.database, err) 991 | )); 992 | } 993 | 994 | freeReplyObject(reply); 995 | 996 | /* See if we've got a qual we can push down */ 997 | if (node->ss.ps.plan->qual) 998 | { 999 | ListCell *lc; 1000 | 1001 | foreach(lc, node->ss.ps.plan->qual) 1002 | { 1003 | /* Only the first qual can be pushed down to Redis */ 1004 | Expr *state = lfirst(lc); 1005 | 1006 | redisGetQual((Node *) state, 1007 | node->ss.ss_currentRelation->rd_att, 1008 | &qual_key, &qual_value, &pushdown); 1009 | if (pushdown) 1010 | break; 1011 | } 1012 | } 1013 | 1014 | /* Stash away the state info we have already */ 1015 | festate = (RedisFdwExecutionState *) palloc(sizeof(RedisFdwExecutionState)); 1016 | node->fdw_state = (void *) festate; 1017 | festate->context = context; 1018 | festate->reply = NULL; 1019 | festate->row = 0; 1020 | festate->address = table_options.address; 1021 | festate->port = table_options.port; 1022 | festate->keyprefix = table_options.keyprefix; 1023 | festate->keyset = table_options.keyset; 1024 | festate->singleton_key = table_options.singleton_key; 1025 | festate->table_type = table_options.table_type; 1026 | festate->cursor_id = NULL; 1027 | festate->cursor_search_string = NULL; 1028 | 1029 | festate->qual_value = pushdown ? qual_value : NULL; 1030 | 1031 | /* OK, we connected. If this is an EXPLAIN, bail out now */ 1032 | if (eflags & EXEC_FLAG_EXPLAIN_ONLY) 1033 | return; 1034 | 1035 | /* 1036 | * We're going to use the current scan-lived context to 1037 | * store the pstrduped cusrsor id. 1038 | */ 1039 | festate->mctxt = CurrentMemoryContext; 1040 | 1041 | /* Execute the query */ 1042 | if (festate->singleton_key) 1043 | { 1044 | /* 1045 | * We're not using cursors for now for singleton key tables. The 1046 | * theory is that we don't expect them to be so large in normal use 1047 | * that we would get any significant benefit from doing so, and in any 1048 | * case scanning them in a single step is not going to tie things up 1049 | * like scannoing the whole Redis database could. 1050 | */ 1051 | 1052 | switch (table_options.table_type) 1053 | { 1054 | case PG_REDIS_SCALAR_TABLE: 1055 | reply = redisCommand(context, "GET %s", festate->singleton_key); 1056 | break; 1057 | case PG_REDIS_HASH_TABLE: 1058 | /* the singleton case where a qual pushdown makes most sense */ 1059 | if (qual_value && pushdown) 1060 | reply = redisCommand(context, "HGET %s %s", festate->singleton_key, qual_value); 1061 | else 1062 | reply = redisCommand(context, "HGETALL %s", festate->singleton_key); 1063 | break; 1064 | case PG_REDIS_LIST_TABLE: 1065 | reply = redisCommand(context, "LRANGE %s 0 -1", table_options.singleton_key); 1066 | break; 1067 | case PG_REDIS_SET_TABLE: 1068 | reply = redisCommand(context, "SMEMBERS %s", table_options.singleton_key); 1069 | break; 1070 | case PG_REDIS_ZSET_TABLE: 1071 | reply = redisCommand(context, "ZRANGEBYSCORE %s -inf inf WITHSCORES", table_options.singleton_key); 1072 | break; 1073 | default: 1074 | ; 1075 | } 1076 | } 1077 | else if (qual_value && pushdown) 1078 | { 1079 | /* 1080 | * if we have a qual, make sure it's a member of the keyset or has the 1081 | * right prefix if either of these options is specified. 1082 | * 1083 | * If not set row to -1 to indicate failure 1084 | */ 1085 | if (festate->keyset) 1086 | { 1087 | redisReply *sreply; 1088 | 1089 | sreply = redisCommand(context, "SISMEMBER %s %s", 1090 | festate->keyset, qual_value); 1091 | if (!sreply) 1092 | { 1093 | redisFree(festate->context); 1094 | ereport(ERROR, 1095 | (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), 1096 | errmsg("failed to list keys: %s", context->errstr) 1097 | )); 1098 | } 1099 | if (sreply->type == REDIS_REPLY_ERROR) 1100 | { 1101 | char *err = pstrdup(sreply->str); 1102 | 1103 | freeReplyObject(sreply); 1104 | ereport(ERROR, 1105 | (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), 1106 | errmsg("failed to list keys: %s", err) 1107 | )); 1108 | 1109 | } 1110 | 1111 | if (sreply->integer != 1) 1112 | festate->row = -1; 1113 | 1114 | } 1115 | else if (festate->keyprefix) 1116 | { 1117 | if (strncmp(qual_value, festate->keyprefix, 1118 | strlen(festate->keyprefix)) != 0) 1119 | festate->row = -1; 1120 | } 1121 | 1122 | /* 1123 | * For a qual we don't want to scan at all, just check that the key 1124 | * exists. We do this check in adddition to the keyset/keyprefix 1125 | * checks, is any, so we know the item is really there. 1126 | */ 1127 | 1128 | reply = redisCommand(context, "EXISTS %s", qual_value); 1129 | if (reply->integer == 0) 1130 | festate->row = -1; 1131 | 1132 | } 1133 | else 1134 | { 1135 | /* no qual - do a cursor scan */ 1136 | if (festate->keyset) 1137 | { 1138 | festate->cursor_search_string = "SSCAN %s %s" COUNT; 1139 | reply = redisCommand(context, festate->cursor_search_string, 1140 | festate->keyset, ZERO); 1141 | } 1142 | else if (festate->keyprefix) 1143 | { 1144 | festate->cursor_search_string = "SCAN %s MATCH %s*" COUNT; 1145 | reply = redisCommand(context, festate->cursor_search_string, 1146 | ZERO, festate->keyprefix); 1147 | } 1148 | else 1149 | { 1150 | festate->cursor_search_string = "SCAN %s" COUNT; 1151 | reply = redisCommand(context, festate->cursor_search_string, ZERO); 1152 | } 1153 | } 1154 | 1155 | if (!reply) 1156 | { 1157 | redisFree(festate->context); 1158 | ereport(ERROR, 1159 | (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), 1160 | errmsg("failed to list keys: %s", context->errstr) 1161 | )); 1162 | } 1163 | else if (reply->type == REDIS_REPLY_ERROR) 1164 | { 1165 | char *err = pstrdup(reply->str); 1166 | 1167 | freeReplyObject(reply); 1168 | ereport(ERROR, 1169 | (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), 1170 | errmsg("failed somehow: %s", err) 1171 | )); 1172 | } 1173 | 1174 | /* Store the additional state info */ 1175 | festate->attinmeta = 1176 | TupleDescGetAttInMetadata(node->ss.ss_currentRelation->rd_att); 1177 | 1178 | if (festate->singleton_key) 1179 | { 1180 | festate->reply = reply; 1181 | } 1182 | else if (festate->row > -1 && festate->qual_value == NULL) 1183 | { 1184 | redisReply *cursor = reply->element[0]; 1185 | 1186 | if (cursor->type == REDIS_REPLY_STRING) 1187 | { 1188 | if (cursor->len == 1 && cursor->str[0] == '0') 1189 | festate->cursor_id = NULL; 1190 | else 1191 | festate->cursor_id = pstrdup(cursor->str); 1192 | } 1193 | else 1194 | { 1195 | ereport(ERROR, 1196 | (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), 1197 | errmsg("wrong reply type %d", cursor->type) 1198 | )); 1199 | } 1200 | 1201 | /* for cursors, this is the list of elements */ 1202 | festate->reply = reply->element[1]; 1203 | } 1204 | } 1205 | 1206 | /* 1207 | * redisIterateForeignScan 1208 | * Read next record from the data file and store it into the 1209 | * ScanTupleSlot as a virtual tuple 1210 | * 1211 | * We have now spearated this into two streams of logic - one 1212 | * for singleton key tables and one for multi-key tables. 1213 | */ 1214 | static TupleTableSlot * 1215 | redisIterateForeignScan(ForeignScanState *node) 1216 | { 1217 | RedisFdwExecutionState *festate = (RedisFdwExecutionState *) node->fdw_state; 1218 | 1219 | if (festate->singleton_key) 1220 | return redisIterateForeignScanSingleton(node); 1221 | else 1222 | return redisIterateForeignScanMulti(node); 1223 | } 1224 | 1225 | static inline TupleTableSlot * 1226 | redisIterateForeignScanMulti(ForeignScanState *node) 1227 | { 1228 | bool found; 1229 | redisReply *reply = 0; 1230 | char *key; 1231 | char *data = 0; 1232 | char **values; 1233 | HeapTuple tuple; 1234 | 1235 | RedisFdwExecutionState *festate = (RedisFdwExecutionState *) node->fdw_state; 1236 | TupleTableSlot *slot = node->ss.ss_ScanTupleSlot; 1237 | 1238 | #ifdef DEBUG 1239 | elog(NOTICE, "redisIterateForeignScanMulti"); 1240 | #endif 1241 | 1242 | /* Cleanup */ 1243 | ExecClearTuple(slot); 1244 | 1245 | /* Get the next record, and set found */ 1246 | found = false; 1247 | 1248 | /* 1249 | * If we're out of rows on the cursor, fetch the next set. Keep going 1250 | * until we get a result back that actually has some rows. 1251 | */ 1252 | while (festate->cursor_id != NULL && 1253 | festate->row >= festate->reply->elements) 1254 | { 1255 | redisReply *creply; 1256 | redisReply *cursor; 1257 | 1258 | Assert(festate->qual_value == NULL); 1259 | 1260 | if (festate->keyset) 1261 | { 1262 | creply = redisCommand(festate->context, 1263 | festate->cursor_search_string, 1264 | festate->keyset, festate->cursor_id); 1265 | } 1266 | else if (festate->keyprefix) 1267 | { 1268 | creply = redisCommand(festate->context, 1269 | festate->cursor_search_string, 1270 | festate->cursor_id, festate->keyprefix); 1271 | } 1272 | else 1273 | { 1274 | creply = redisCommand(festate->context, 1275 | festate->cursor_search_string, 1276 | festate->cursor_id); 1277 | } 1278 | 1279 | if (!creply) 1280 | { 1281 | redisFree(festate->context); 1282 | ereport(ERROR, 1283 | (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), 1284 | errmsg("failed to list keys: %s", 1285 | festate->context->errstr) 1286 | )); 1287 | } 1288 | else if (creply->type == REDIS_REPLY_ERROR) 1289 | { 1290 | char *err = pstrdup(creply->str); 1291 | 1292 | freeReplyObject(creply); 1293 | ereport(ERROR, 1294 | (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), 1295 | errmsg("failed somehow: %s", err) 1296 | )); 1297 | } 1298 | 1299 | cursor = creply->element[0]; 1300 | 1301 | if (cursor->type == REDIS_REPLY_STRING) 1302 | { 1303 | 1304 | MemoryContext oldcontext; 1305 | oldcontext = MemoryContextSwitchTo(festate->mctxt); 1306 | pfree(festate->cursor_id); 1307 | if (cursor->len == 1 && cursor->str[0] == '0') 1308 | festate->cursor_id = NULL; 1309 | else 1310 | festate->cursor_id = pstrdup(cursor->str); 1311 | MemoryContextSwitchTo(oldcontext); 1312 | } 1313 | else 1314 | { 1315 | ereport(ERROR, 1316 | (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), 1317 | errmsg("wrong reply type %d", cursor->type) 1318 | )); 1319 | } 1320 | 1321 | festate->reply = creply->element[1]; 1322 | festate->row = 0; 1323 | } 1324 | 1325 | /* 1326 | * -1 means we failed the qual test, so there are no rows or we've already 1327 | * processed the qual 1328 | */ 1329 | 1330 | if (festate->row > -1 && 1331 | (festate->qual_value != NULL || 1332 | (festate->row < festate->reply->elements))) 1333 | { 1334 | /* 1335 | * Get the row, check the result type, and handle accordingly. If it's 1336 | * nil, we go ahead and get the next row. 1337 | */ 1338 | do 1339 | { 1340 | 1341 | key = festate->qual_value != NULL ? 1342 | festate->qual_value : 1343 | festate->reply->element[festate->row]->str; 1344 | switch (festate->table_type) 1345 | { 1346 | case PG_REDIS_HASH_TABLE: 1347 | reply = redisCommand(festate->context, 1348 | "HGETALL %s", key); 1349 | break; 1350 | case PG_REDIS_LIST_TABLE: 1351 | reply = redisCommand(festate->context, 1352 | "LRANGE %s 0 -1", key); 1353 | break; 1354 | case PG_REDIS_SET_TABLE: 1355 | reply = redisCommand(festate->context, 1356 | "SMEMBERS %s", key); 1357 | break; 1358 | case PG_REDIS_ZSET_TABLE: 1359 | reply = redisCommand(festate->context, 1360 | "ZRANGE %s 0 -1", key); 1361 | break; 1362 | case PG_REDIS_SCALAR_TABLE: 1363 | default: 1364 | reply = redisCommand(festate->context, 1365 | "GET %s", key); 1366 | } 1367 | 1368 | if (!reply) 1369 | { 1370 | freeReplyObject(festate->reply); 1371 | redisFree(festate->context); 1372 | ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_REPLY), 1373 | errmsg("failed to get the value for key \"%s\": %s", 1374 | key, festate->context->errstr) 1375 | )); 1376 | } 1377 | 1378 | festate->row++; 1379 | 1380 | } while ((reply->type == REDIS_REPLY_NIL || 1381 | reply->type == REDIS_REPLY_STATUS || 1382 | reply->type == REDIS_REPLY_ERROR) && 1383 | festate->qual_value == NULL && 1384 | festate->row < festate->reply->elements); 1385 | 1386 | if (festate->qual_value != NULL || festate->row <= festate->reply->elements) 1387 | { 1388 | /* 1389 | * Now, deal with the different data types we might have got from 1390 | * Redis. 1391 | */ 1392 | 1393 | switch (reply->type) 1394 | { 1395 | case REDIS_REPLY_INTEGER: 1396 | data = (char *) palloc(sizeof(char) * 64); 1397 | snprintf(data, 64, "%lld", reply->integer); 1398 | found = true; 1399 | break; 1400 | 1401 | case REDIS_REPLY_STRING: 1402 | data = reply->str; 1403 | found = true; 1404 | break; 1405 | 1406 | case REDIS_REPLY_ARRAY: 1407 | data = process_redis_array(reply, festate->table_type); 1408 | found = true; 1409 | break; 1410 | } 1411 | } 1412 | 1413 | /* make sure we don't try to process the qual row twice */ 1414 | if (festate->qual_value != NULL) 1415 | festate->row = -1; 1416 | } 1417 | 1418 | /* Build the tuple */ 1419 | if (found) 1420 | { 1421 | values = (char **) palloc(sizeof(char *) * 2); 1422 | values[0] = key; 1423 | values[1] = data; 1424 | tuple = BuildTupleFromCStrings(festate->attinmeta, values); 1425 | ExecStoreHeapTuple(tuple, slot, false); 1426 | } 1427 | 1428 | /* Cleanup */ 1429 | if (reply) 1430 | freeReplyObject(reply); 1431 | 1432 | return slot; 1433 | } 1434 | 1435 | static inline TupleTableSlot * 1436 | redisIterateForeignScanSingleton(ForeignScanState *node) 1437 | { 1438 | bool found; 1439 | char *key = NULL; 1440 | char *data = NULL; 1441 | char **values; 1442 | HeapTuple tuple; 1443 | 1444 | RedisFdwExecutionState *festate = (RedisFdwExecutionState *) node->fdw_state; 1445 | TupleTableSlot *slot = node->ss.ss_ScanTupleSlot; 1446 | 1447 | #ifdef DEBUG 1448 | elog(NOTICE, "redisIterateForeignScanSingleton"); 1449 | #endif 1450 | 1451 | /* Cleanup */ 1452 | ExecClearTuple(slot); 1453 | 1454 | if (festate->row < 0) 1455 | return slot; 1456 | 1457 | /* Get the next record, and set found */ 1458 | found = false; 1459 | 1460 | if (festate->table_type == PG_REDIS_SCALAR_TABLE) 1461 | { 1462 | festate->row = -1; /* just one row for a scalar */ 1463 | switch (festate->reply->type) 1464 | { 1465 | case REDIS_REPLY_INTEGER: 1466 | key = (char *) palloc(sizeof(char) * 64); 1467 | snprintf(key, 64, "%lld", festate->reply->integer); 1468 | found = true; 1469 | break; 1470 | 1471 | case REDIS_REPLY_STRING: 1472 | key = festate->reply->str; 1473 | found = true; 1474 | break; 1475 | 1476 | case REDIS_REPLY_ARRAY: 1477 | freeReplyObject(festate->reply); 1478 | redisFree(festate->context); 1479 | ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_REPLY), 1480 | errmsg("not expecting an array for a singleton scalar table"))); 1481 | break; 1482 | } 1483 | } 1484 | else if (festate->table_type == PG_REDIS_HASH_TABLE && festate->qual_value) 1485 | { 1486 | festate->row = -1; /* just one row for qual'd search in a hash */ 1487 | key = festate->qual_value; 1488 | switch (festate->reply->type) 1489 | { 1490 | case REDIS_REPLY_INTEGER: 1491 | data = (char *) palloc(sizeof(char) * 64); 1492 | snprintf(data, 64, "%lld", festate->reply->integer); 1493 | found = true; 1494 | break; 1495 | 1496 | case REDIS_REPLY_STRING: 1497 | data = festate->reply->str; 1498 | found = true; 1499 | break; 1500 | 1501 | case REDIS_REPLY_ARRAY: 1502 | freeReplyObject(festate->reply); 1503 | redisFree(festate->context); 1504 | ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_REPLY), 1505 | errmsg("not expecting an array for a single hash property: %s", festate->qual_value))); 1506 | break; 1507 | } 1508 | } 1509 | else if (festate->row < festate->reply->elements) 1510 | { 1511 | /* everything else comes in as an array reply type */ 1512 | found = true; 1513 | key = festate->reply->element[festate->row]->str; 1514 | festate->row++; 1515 | if (festate->table_type == PG_REDIS_HASH_TABLE || 1516 | festate->table_type == PG_REDIS_ZSET_TABLE) 1517 | { 1518 | redisReply *dreply = festate->reply->element[festate->row]; 1519 | 1520 | switch (dreply->type) 1521 | { 1522 | case REDIS_REPLY_INTEGER: 1523 | data = (char *) palloc(sizeof(char) * 64); 1524 | snprintf(key, 64, "%lld", dreply->integer); 1525 | break; 1526 | 1527 | case REDIS_REPLY_STRING: 1528 | data = dreply->str; 1529 | break; 1530 | 1531 | case REDIS_REPLY_ARRAY: 1532 | freeReplyObject(festate->reply); 1533 | redisFree(festate->context); 1534 | ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_REPLY), 1535 | errmsg("not expecting array for a hash value or zset score") 1536 | )); 1537 | break; 1538 | } 1539 | festate->row++; 1540 | } 1541 | } 1542 | 1543 | /* Build the tuple */ 1544 | values = (char **) palloc(sizeof(char *) * 2); 1545 | 1546 | if (found) 1547 | { 1548 | values[0] = key; 1549 | values[1] = data; 1550 | tuple = BuildTupleFromCStrings(festate->attinmeta, values); 1551 | ExecStoreHeapTuple(tuple, slot, false); 1552 | } 1553 | 1554 | return slot; 1555 | } 1556 | 1557 | /* 1558 | * redisEndForeignScan 1559 | * Finish scanning foreign table and dispose objects used for this scan 1560 | */ 1561 | static void 1562 | redisEndForeignScan(ForeignScanState *node) 1563 | { 1564 | RedisFdwExecutionState *festate = (RedisFdwExecutionState *) node->fdw_state; 1565 | 1566 | #ifdef DEBUG 1567 | elog(NOTICE, "redisEndForeignScan"); 1568 | #endif 1569 | 1570 | /* if festate is NULL, we are in EXPLAIN; nothing to do */ 1571 | if (festate) 1572 | { 1573 | if (festate->reply) 1574 | freeReplyObject(festate->reply); 1575 | 1576 | if (festate->context) 1577 | redisFree(festate->context); 1578 | } 1579 | } 1580 | 1581 | /* 1582 | * redisReScanForeignScan 1583 | * Rescan table, possibly with new parameters 1584 | */ 1585 | static void 1586 | redisReScanForeignScan(ForeignScanState *node) 1587 | { 1588 | RedisFdwExecutionState *festate = (RedisFdwExecutionState *) node->fdw_state; 1589 | 1590 | #ifdef DEBUG 1591 | elog(NOTICE, "redisReScanForeignScan"); 1592 | #endif 1593 | 1594 | if (festate->row > -1) 1595 | festate->row = 0; 1596 | } 1597 | 1598 | static void 1599 | redisGetQual(Node *node, TupleDesc tupdesc, char **key, char **value, bool *pushdown) 1600 | { 1601 | *key = NULL; 1602 | *value = NULL; 1603 | *pushdown = false; 1604 | 1605 | if (!node) 1606 | return; 1607 | 1608 | if (IsA(node, OpExpr)) 1609 | { 1610 | OpExpr *op = (OpExpr *) node; 1611 | Node *left, 1612 | *right; 1613 | Index varattno; 1614 | 1615 | if (list_length(op->args) != 2) 1616 | return; 1617 | 1618 | left = list_nth(op->args, 0); 1619 | 1620 | if (!IsA(left, Var)) 1621 | return; 1622 | 1623 | varattno = ((Var *) left)->varattno; 1624 | 1625 | right = list_nth(op->args, 1); 1626 | 1627 | if (IsA(right, Const)) 1628 | { 1629 | StringInfoData buf; 1630 | 1631 | initStringInfo(&buf); 1632 | 1633 | /* And get the column and value... */ 1634 | *key = NameStr(TupleDescAttr(tupdesc, varattno - 1)->attname); 1635 | *value = TextDatumGetCString(((Const *) right)->constvalue); 1636 | 1637 | /* 1638 | * We can push down this qual if: - The operatory is TEXTEQ - The 1639 | * qual is on the key column 1640 | */ 1641 | if (op->opfuncid == PROCID_TEXTEQ && strcmp(*key, "key") == 0) 1642 | *pushdown = true; 1643 | 1644 | return; 1645 | } 1646 | } 1647 | return; 1648 | } 1649 | 1650 | /* 1651 | * process_redis_array 1652 | * Returns StringInfo->data as char * for a Redis reply internal group of values 1653 | */ 1654 | static char * 1655 | process_redis_array(redisReply *reply, redis_table_type type) 1656 | { 1657 | StringInfo res = makeStringInfo(); 1658 | bool need_sep = false; 1659 | 1660 | appendStringInfoChar(res, '{'); 1661 | for (int i = 0; i < reply->elements; i++) 1662 | { 1663 | redisReply *ir = reply->element[i]; 1664 | 1665 | if (need_sep) 1666 | appendStringInfoChar(res, ','); 1667 | need_sep = true; 1668 | if (ir->type == REDIS_REPLY_ARRAY) 1669 | ereport(ERROR, 1670 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), /* ??? */ 1671 | errmsg("nested array returns not yet supported"))); 1672 | switch (ir->type) 1673 | { 1674 | case REDIS_REPLY_STATUS: 1675 | case REDIS_REPLY_STRING: 1676 | { 1677 | char *buff; 1678 | char *crs; 1679 | 1680 | pg_verifymbstr(ir->str, ir->len, false); 1681 | buff = palloc(ir->len * 2 + 3); 1682 | crs = buff; 1683 | *crs++ = '"'; 1684 | for (int j = 0; j < ir->len; j++) 1685 | { 1686 | if (ir->str[j] == '"' || ir->str[j] == '\\') 1687 | *crs++ = '\\'; 1688 | *crs++ = ir->str[j]; 1689 | } 1690 | *crs++ = '"'; 1691 | *crs = '\0'; 1692 | appendStringInfoString(res, buff); 1693 | pfree(buff); 1694 | } 1695 | break; 1696 | case REDIS_REPLY_INTEGER: 1697 | appendStringInfo(res, "%lld", ir->integer); 1698 | break; 1699 | case REDIS_REPLY_NIL: 1700 | appendStringInfoString(res, "NULL"); 1701 | break; 1702 | default: 1703 | break; 1704 | } 1705 | } 1706 | appendStringInfoChar(res, '}'); 1707 | 1708 | return res->data; 1709 | } 1710 | 1711 | static void 1712 | redisAddForeignUpdateTargets(PlannerInfo *root, 1713 | Index rtindex, 1714 | RangeTblEntry *target_rte, 1715 | Relation target_relation) 1716 | { 1717 | Var *var; 1718 | 1719 | /* assumes that this isn't attisdropped */ 1720 | Form_pg_attribute attr = 1721 | TupleDescAttr(RelationGetDescr(target_relation), 0); 1722 | 1723 | #ifdef DEBUG 1724 | elog(NOTICE, "redisAddForeignUpdateTargets"); 1725 | #endif 1726 | 1727 | /* 1728 | * Code adapted from postgres_fdw 1729 | * 1730 | * In Redis, we need the key name. It's the first column in the table 1731 | * regardless of the table type. Knowing the key, we can update or delete 1732 | * it. 1733 | */ 1734 | 1735 | /* Make a Var representing the desired value */ 1736 | var = makeVar(rtindex, 1737 | 1, 1738 | attr->atttypid, 1739 | attr->atttypmod, 1740 | InvalidOid, 1741 | 0); 1742 | 1743 | /* register it as a row-identity column needed by this target rel */ 1744 | add_row_identity_var(root, var, rtindex, REDISMODKEYNAME); 1745 | } 1746 | 1747 | /* 1748 | * redisPlanForeignModify 1749 | * Plan an insert/update/delete operation on a foreign table 1750 | */ 1751 | static List * 1752 | redisPlanForeignModify(PlannerInfo *root, 1753 | ModifyTable *plan, 1754 | Index resultRelation, 1755 | int subplan_index) 1756 | { 1757 | CmdType operation = plan->operation; 1758 | RangeTblEntry *rte = planner_rt_fetch(resultRelation, root); 1759 | Relation rel; 1760 | List *targetAttrs = NIL; 1761 | List *array_elem_list = NIL; 1762 | TupleDesc tupdesc; 1763 | Oid array_element_type = InvalidOid; 1764 | 1765 | #ifdef DEBUG 1766 | elog(NOTICE, "redisPlanForeignModify"); 1767 | #endif 1768 | 1769 | /* 1770 | * RETURNING list not supported 1771 | */ 1772 | if (plan->returningLists) 1773 | elog(ERROR, "RETURNING is not supported by this FDW"); 1774 | 1775 | rel = table_open(rte->relid, NoLock); 1776 | tupdesc = RelationGetDescr(rel); 1777 | 1778 | /* if the second attribute exists and it's an array, get the element type */ 1779 | if (tupdesc->natts > 1) 1780 | { 1781 | Form_pg_attribute attr = TupleDescAttr(tupdesc, 1); 1782 | 1783 | array_element_type = get_element_type(attr->atttypid); 1784 | } 1785 | 1786 | array_elem_list = lappend_oid(array_elem_list, array_element_type); 1787 | 1788 | 1789 | if (operation == CMD_INSERT) 1790 | { 1791 | int attnum; 1792 | 1793 | for (attnum = 1; attnum <= tupdesc->natts; attnum++) 1794 | { 1795 | Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1); 1796 | 1797 | if (!attr->attisdropped) 1798 | targetAttrs = lappend_int(targetAttrs, attnum); 1799 | } 1800 | } 1801 | else if (operation == CMD_UPDATE) 1802 | { 1803 | 1804 | /* code borrowed from mysql fdw */ 1805 | 1806 | RelOptInfo *rrel = find_base_rel(root, resultRelation); 1807 | Bitmapset *tmpset = get_rel_all_updated_cols(root, rrel); 1808 | int colidx = -1; 1809 | 1810 | while ((colidx = bms_next_member(tmpset, colidx)) >= 0) 1811 | { 1812 | AttrNumber col = colidx + FirstLowInvalidHeapAttributeNumber; 1813 | if (col <= InvalidAttrNumber) /* shouldn't happen */ 1814 | elog(ERROR, "system-column update is not supported"); 1815 | 1816 | targetAttrs = lappend_int(targetAttrs, col); 1817 | } 1818 | } 1819 | 1820 | /* nothing extra needed for DELETE - all it needs is the resjunk column */ 1821 | table_close(rel, NoLock); 1822 | 1823 | return list_make2(targetAttrs, array_elem_list); 1824 | } 1825 | 1826 | /* 1827 | * redisBeginForeignModify 1828 | * Begin an insert/update/delete operation on a foreign table 1829 | */ 1830 | static void 1831 | redisBeginForeignModify(ModifyTableState *mtstate, 1832 | ResultRelInfo *rinfo, 1833 | List *fdw_private, 1834 | int subplan_index, 1835 | int eflags) 1836 | { 1837 | redisTableOptions table_options; 1838 | redisContext *context; 1839 | redisReply *reply; 1840 | RedisFdwModifyState *fmstate; 1841 | struct timeval timeout = {1, 500000}; 1842 | Relation rel = rinfo->ri_RelationDesc; 1843 | ListCell *lc; 1844 | Oid typefnoid; 1845 | bool isvarlena; 1846 | CmdType op = mtstate->operation; 1847 | int n_attrs; 1848 | List *array_elem_list; 1849 | 1850 | #ifdef DEBUG 1851 | elog(NOTICE, "redisBeginForeignModify"); 1852 | #endif 1853 | 1854 | /* Fetch options */ 1855 | redisGetOptions(RelationGetRelid(rel), 1856 | &table_options); 1857 | 1858 | fmstate = (RedisFdwModifyState *) palloc(sizeof(RedisFdwModifyState)); 1859 | rinfo->ri_FdwState = fmstate; 1860 | fmstate->rel = rel; 1861 | fmstate->address = table_options.address; 1862 | fmstate->port = table_options.port; 1863 | fmstate->keyprefix = table_options.keyprefix; 1864 | fmstate->keyset = table_options.keyset; 1865 | fmstate->singleton_key = table_options.singleton_key; 1866 | fmstate->table_type = table_options.table_type; 1867 | fmstate->target_attrs = (List *) list_nth(fdw_private, 0); 1868 | 1869 | n_attrs = list_length(fmstate->target_attrs); 1870 | fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * (n_attrs + 1)); 1871 | fmstate->targetDims = (int *) palloc0(sizeof(int) * (n_attrs + 1)); 1872 | 1873 | array_elem_list = (List *) list_nth(fdw_private, 1); 1874 | fmstate->array_elem_type = list_nth_oid(array_elem_list, 0); 1875 | 1876 | fmstate->p_nums = 0; 1877 | 1878 | if (op == CMD_UPDATE || op == CMD_DELETE) 1879 | { 1880 | Plan *subplan = outerPlanState(mtstate)->plan; 1881 | Form_pg_attribute attr = TupleDescAttr(RelationGetDescr(rel), 0); /* key is first */ 1882 | 1883 | fmstate->keyAttno = ExecFindJunkAttributeInTlist(subplan->targetlist, 1884 | REDISMODKEYNAME); 1885 | 1886 | getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena); 1887 | fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]); 1888 | fmstate->p_nums++; 1889 | } 1890 | 1891 | if (op == CMD_UPDATE || op == CMD_INSERT) 1892 | { 1893 | fmstate->targetDims = (int *) palloc0(sizeof(int) * (n_attrs + 1)); 1894 | 1895 | foreach(lc, fmstate->target_attrs) 1896 | { 1897 | int attnum = lfirst_int(lc); 1898 | Form_pg_attribute attr = TupleDescAttr(RelationGetDescr(rel), attnum - 1); 1899 | Oid elem = attr->attndims ? 1900 | get_element_type(attr->atttypid) : 1901 | attr->atttypid; 1902 | 1903 | /* 1904 | * most non-singleton table types require an array, not text as 1905 | * value 1906 | */ 1907 | if (op == CMD_UPDATE && attnum > 1 && 1908 | attr->attndims == 0 && !fmstate->singleton_key && 1909 | fmstate->table_type != PG_REDIS_SCALAR_TABLE) 1910 | { 1911 | ereport(ERROR, 1912 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 1913 | errmsg("value update not supported for this type of table") 1914 | )); 1915 | } 1916 | 1917 | /* 1918 | * If the item is an array, store the output details for its 1919 | * element type, otherwise for the actual type. This saves us 1920 | * doing lookups later on. 1921 | */ 1922 | fmstate->targetDims[fmstate->p_nums] = attr->attndims; 1923 | getTypeOutputInfo(elem, &typefnoid, &isvarlena); 1924 | fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]); 1925 | fmstate->p_nums++; 1926 | } 1927 | } 1928 | 1929 | /* 1930 | * Now do some sanity checking on the number of table attributes. Since we 1931 | * do these here we can assume everthing is OK when we do the per row 1932 | * functions. 1933 | */ 1934 | 1935 | if (op == CMD_INSERT) 1936 | { 1937 | if (table_options.singleton_key) 1938 | { 1939 | if (table_options.table_type == PG_REDIS_ZSET_TABLE && fmstate->p_nums < 2) 1940 | ereport(ERROR, 1941 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 1942 | errmsg("operation not supported for singleton zset " 1943 | "table without priorities column") 1944 | )); 1945 | else if (fmstate->p_nums != ((table_options.table_type == PG_REDIS_HASH_TABLE || table_options.table_type == PG_REDIS_ZSET_TABLE) ? 2 : 1)) 1946 | ereport(ERROR, 1947 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 1948 | errmsg("table has incorrect number of columns: %d for type %d", fmstate->p_nums, table_options.table_type) 1949 | )); 1950 | } 1951 | else if (fmstate->p_nums != 2) 1952 | { 1953 | ereport(ERROR, 1954 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 1955 | errmsg("table has incorrect number of columns") 1956 | )); 1957 | } 1958 | } 1959 | else if (op == CMD_UPDATE) 1960 | { 1961 | if (table_options.singleton_key && fmstate->table_type == PG_REDIS_LIST_TABLE) 1962 | ereport(ERROR, 1963 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 1964 | errmsg("update not supported for this type of table") 1965 | )); 1966 | } 1967 | else /* DELETE */ 1968 | { 1969 | if (table_options.singleton_key && fmstate->table_type == PG_REDIS_LIST_TABLE) 1970 | ereport(ERROR, 1971 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 1972 | errmsg("delete not supported for this type of table") 1973 | )); 1974 | } 1975 | 1976 | /* 1977 | * all the checks have been done but no actual work done or connections 1978 | * made. That makes this the right spot to return if we're doing explain 1979 | * only. 1980 | */ 1981 | 1982 | if (eflags & EXEC_FLAG_EXPLAIN_ONLY) 1983 | return; 1984 | 1985 | /* Finally, Connect to the server and set the Redis execution context */ 1986 | context = redisConnectWithTimeout(table_options.address, 1987 | table_options.port, timeout); 1988 | 1989 | if (context->err) 1990 | { 1991 | redisFree(context); 1992 | ereport(ERROR, 1993 | (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), 1994 | errmsg("failed to connect to Redis: %s", context->errstr) 1995 | )); 1996 | } 1997 | 1998 | /* Authenticate */ 1999 | if (table_options.password) 2000 | { 2001 | reply = redisCommand(context, "AUTH %s", table_options.password); 2002 | 2003 | if (!reply) 2004 | { 2005 | redisFree(context); 2006 | ereport(ERROR, 2007 | (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), 2008 | errmsg("failed to authenticate to redis: %s", context->errstr) 2009 | )); 2010 | } 2011 | 2012 | freeReplyObject(reply); 2013 | } 2014 | 2015 | /* Select the appropriate database */ 2016 | reply = redisCommand(context, "SELECT %d", table_options.database); 2017 | 2018 | check_reply(reply, context, ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION, 2019 | "failed to select database", NULL); 2020 | 2021 | freeReplyObject(reply); 2022 | 2023 | fmstate->context = context; 2024 | } 2025 | 2026 | static void 2027 | check_reply(redisReply *reply, redisContext *context, int error_code, char *message, char *arg) 2028 | { 2029 | char *err; 2030 | char *xmessage; 2031 | int msglen; 2032 | 2033 | if (!reply) 2034 | { 2035 | err = pstrdup(context->errstr); 2036 | redisFree(context); 2037 | } 2038 | else if (reply->type == REDIS_REPLY_ERROR) 2039 | { 2040 | err = pstrdup(reply->str); 2041 | freeReplyObject(reply); 2042 | } 2043 | else 2044 | return; 2045 | 2046 | msglen = strlen(message); 2047 | xmessage = palloc(msglen + 6); 2048 | strncpy(xmessage, message, msglen + 1); 2049 | strcat(xmessage, ": %s"); 2050 | 2051 | if (arg != NULL) 2052 | ereport(ERROR, 2053 | (errcode(error_code), 2054 | errmsg(xmessage, arg, err) 2055 | )); 2056 | else 2057 | ereport(ERROR, 2058 | (errcode(error_code), 2059 | errmsg(xmessage, err) 2060 | )); 2061 | } 2062 | 2063 | /* 2064 | * redisExecForeignInsert 2065 | * Insert one row into a foreign table 2066 | */ 2067 | static TupleTableSlot * 2068 | redisExecForeignInsert(EState *estate, 2069 | ResultRelInfo *rinfo, 2070 | TupleTableSlot *slot, 2071 | TupleTableSlot *planSlot) 2072 | { 2073 | RedisFdwModifyState *fmstate = 2074 | (RedisFdwModifyState *) rinfo->ri_FdwState; 2075 | redisContext *context = fmstate->context; 2076 | redisReply *sreply = NULL; 2077 | bool isnull; 2078 | Datum key; 2079 | char *keyval; 2080 | char *extraval = ""; /* hash value or zset priority */ 2081 | 2082 | #ifdef DEBUG 2083 | elog(NOTICE, "redisExecForeignInsert"); 2084 | #endif 2085 | 2086 | key = slot_getattr(slot, 1, &isnull); 2087 | keyval = OutputFunctionCall(&fmstate->p_flinfo[0], key); 2088 | 2089 | if (fmstate->singleton_key) 2090 | { 2091 | char *rkeyval; 2092 | 2093 | if (fmstate->table_type == PG_REDIS_SCALAR_TABLE) 2094 | rkeyval = fmstate->singleton_key; 2095 | else 2096 | rkeyval = keyval; 2097 | 2098 | /* 2099 | * Check if key is there using EXISTS / HEXISTS / SISMEMBER / ZRANK. 2100 | * It is not an error for a list type singleton as they don't have to 2101 | * be unique. 2102 | */ 2103 | 2104 | switch (fmstate->table_type) 2105 | { 2106 | case PG_REDIS_SCALAR_TABLE: 2107 | sreply = redisCommand(context, "EXISTS %s", /* 1 or 0 */ 2108 | fmstate->singleton_key); 2109 | break; 2110 | case PG_REDIS_HASH_TABLE: 2111 | sreply = redisCommand(context, "HEXISTS %s %s", /* 1 or 0 */ 2112 | fmstate->singleton_key, keyval); 2113 | break; 2114 | case PG_REDIS_SET_TABLE: 2115 | sreply = redisCommand(context, "SISMEMBER %s %s", /* 1 or 0 */ 2116 | fmstate->singleton_key, keyval); 2117 | break; 2118 | case PG_REDIS_ZSET_TABLE: 2119 | sreply = redisCommand(context, "ZRANK %s %s", /* n or nil */ 2120 | 2121 | fmstate->singleton_key, keyval); 2122 | break; 2123 | case PG_REDIS_LIST_TABLE: 2124 | default: 2125 | break; 2126 | } 2127 | 2128 | if (fmstate->table_type != PG_REDIS_LIST_TABLE) 2129 | { 2130 | bool ok = true; 2131 | 2132 | check_reply(sreply, context, ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2133 | "failed checking key existence", NULL); 2134 | 2135 | if (fmstate->table_type != PG_REDIS_ZSET_TABLE) 2136 | ok = sreply->type == REDIS_REPLY_INTEGER && 2137 | sreply->integer == 0; 2138 | else 2139 | ok = sreply->type == REDIS_REPLY_NIL; 2140 | 2141 | freeReplyObject(sreply); 2142 | 2143 | if (!ok) 2144 | { 2145 | ereport(ERROR, 2146 | (errcode(ERRCODE_UNIQUE_VIOLATION), 2147 | errmsg("key already exists: %s", rkeyval))); 2148 | } 2149 | } 2150 | 2151 | /* if OK add the value using SET / HSET / SADD / ZADD / RPUSH */ 2152 | 2153 | /* get the second value for appropriate table types */ 2154 | 2155 | if (fmstate->table_type == PG_REDIS_ZSET_TABLE || 2156 | fmstate->table_type == PG_REDIS_HASH_TABLE) 2157 | { 2158 | Datum extra; 2159 | 2160 | extra = slot_getattr(slot, 2, &isnull); 2161 | extraval = OutputFunctionCall(&fmstate->p_flinfo[1], extra); 2162 | } 2163 | 2164 | switch (fmstate->table_type) 2165 | { 2166 | case PG_REDIS_SCALAR_TABLE: 2167 | sreply = redisCommand(context, "SET %s %s", 2168 | fmstate->singleton_key, keyval); 2169 | break; 2170 | case PG_REDIS_SET_TABLE: 2171 | sreply = redisCommand(context, "SADD %s %s", 2172 | fmstate->singleton_key, keyval); 2173 | break; 2174 | case PG_REDIS_LIST_TABLE: 2175 | sreply = redisCommand(context, "RPUSH %s %s", 2176 | fmstate->singleton_key, keyval); 2177 | break; 2178 | case PG_REDIS_HASH_TABLE: 2179 | sreply = redisCommand(context, "HSET %s %s %s", 2180 | fmstate->singleton_key, keyval, extraval); 2181 | break; 2182 | case PG_REDIS_ZSET_TABLE: 2183 | /* 2184 | * score comes BEFORE value in ZADD, which seems slightly 2185 | * perverse 2186 | */ 2187 | sreply = redisCommand(context, "ZADD %s %s %s", 2188 | fmstate->singleton_key, extraval, keyval); 2189 | break; 2190 | default: 2191 | ereport(ERROR, 2192 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 2193 | errmsg("insert not supported for this type of table") 2194 | )); 2195 | } 2196 | 2197 | check_reply(sreply, context, ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2198 | "cannot insert value for key %s", keyval); 2199 | freeReplyObject(sreply); 2200 | } 2201 | else /* if not a singleton key table */ 2202 | { 2203 | char *valueval = NULL; 2204 | int nitems; 2205 | Datum *elements; 2206 | bool *nulls; 2207 | int16 typlen; 2208 | bool typbyval; 2209 | char typalign; 2210 | bool is_array = fmstate->array_elem_type != InvalidOid; 2211 | Datum value = slot_getattr(slot, 2, &isnull); 2212 | 2213 | if (isnull) 2214 | ereport(ERROR, 2215 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 2216 | errmsg("cannot insert NULL into a Redis table") 2217 | )); 2218 | 2219 | if (is_array && fmstate->table_type == PG_REDIS_SCALAR_TABLE) 2220 | ereport(ERROR, 2221 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 2222 | errmsg("cannot insert array into into a Redis scalar table") 2223 | )); 2224 | else if (!is_array && fmstate->table_type != PG_REDIS_SCALAR_TABLE) 2225 | ereport(ERROR, 2226 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 2227 | errmsg("cannot insert into this type of Redis table - needs an array") 2228 | )); 2229 | 2230 | /* make sure the key has the right prefix, if any */ 2231 | if (fmstate->keyprefix && 2232 | strncmp(keyval, fmstate->keyprefix, 2233 | strlen(fmstate->keyprefix)) != 0) 2234 | ereport(ERROR, 2235 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 2236 | errmsg("key '%s' does not match table key prefix '%s'", 2237 | keyval, fmstate->keyprefix) 2238 | )); 2239 | 2240 | /* Check if key is there using EXISTS */ 2241 | sreply = redisCommand(context, "EXISTS %s", /* 1 or 0 */ 2242 | keyval); 2243 | check_reply(sreply, context, ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2244 | "failed checking key existence", NULL); 2245 | 2246 | if (sreply->type != REDIS_REPLY_INTEGER || sreply->integer != 0) 2247 | { 2248 | freeReplyObject(sreply); 2249 | ereport(ERROR, 2250 | (errcode(ERRCODE_UNIQUE_VIOLATION), 2251 | errmsg("key already exists: %s", keyval))); 2252 | } 2253 | 2254 | freeReplyObject(sreply); 2255 | 2256 | /* if OK add values using SET / HSET / SADD / ZADD / RPUSH */ 2257 | 2258 | if (fmstate->table_type == PG_REDIS_SCALAR_TABLE) 2259 | { 2260 | /* everything else will be an array */ 2261 | valueval = OutputFunctionCall(&fmstate->p_flinfo[1], value); 2262 | } 2263 | else 2264 | { 2265 | int i; 2266 | 2267 | get_typlenbyvalalign(fmstate->array_elem_type, 2268 | &typlen, &typbyval, &typalign); 2269 | 2270 | deconstruct_array(DatumGetArrayTypeP(value), 2271 | fmstate->array_elem_type, typlen, typbyval, 2272 | typalign, &elements, &nulls, 2273 | &nitems); 2274 | 2275 | if (nitems == 0) 2276 | ereport(ERROR, 2277 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 2278 | errmsg("cannot store empty list in a Redis table") 2279 | )); 2280 | 2281 | if (fmstate->table_type == PG_REDIS_HASH_TABLE && nitems % 2 != 0) 2282 | ereport(ERROR, 2283 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 2284 | errmsg("cannot decompose odd number of items into a Redis hash") 2285 | )); 2286 | 2287 | for (i = 0; i < nitems; i++) 2288 | { 2289 | if (nulls[i]) 2290 | ereport(ERROR, 2291 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 2292 | errmsg("cannot insert NULL into a Redis table") 2293 | )); 2294 | } 2295 | } 2296 | 2297 | switch (fmstate->table_type) 2298 | { 2299 | case PG_REDIS_SCALAR_TABLE: 2300 | sreply = redisCommand(context, "SET %s %s", 2301 | keyval, valueval); 2302 | check_reply(sreply, context, 2303 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2304 | "could not add key %s", keyval); 2305 | freeReplyObject(sreply); 2306 | break; 2307 | case PG_REDIS_SET_TABLE: 2308 | { 2309 | int i; 2310 | 2311 | for (i = 0; i < nitems; i++) 2312 | { 2313 | valueval = OutputFunctionCall(&fmstate->p_flinfo[1], 2314 | elements[i]); 2315 | sreply = redisCommand(context, "SADD %s %s", 2316 | keyval, valueval); 2317 | check_reply(sreply, context, 2318 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2319 | "could not add set member %s", valueval); 2320 | freeReplyObject(sreply); 2321 | } 2322 | } 2323 | break; 2324 | case PG_REDIS_LIST_TABLE: 2325 | { 2326 | int i; 2327 | 2328 | for (i = 0; i < nitems; i++) 2329 | { 2330 | valueval = OutputFunctionCall(&fmstate->p_flinfo[1], 2331 | elements[i]); 2332 | sreply = redisCommand(context, "RPUSH %s %s", 2333 | keyval, valueval); 2334 | check_reply(sreply, context, 2335 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2336 | "could not add value %s", valueval); 2337 | } 2338 | } 2339 | break; 2340 | case PG_REDIS_HASH_TABLE: 2341 | { 2342 | int i; 2343 | char *hk, 2344 | *hv; 2345 | 2346 | for (i = 0; i < nitems; i += 2) 2347 | { 2348 | hk = OutputFunctionCall(&fmstate->p_flinfo[1], 2349 | elements[i]); 2350 | hv = OutputFunctionCall(&fmstate->p_flinfo[1], 2351 | elements[i + 1]); 2352 | sreply = redisCommand(context, "HSET %s %s %s", 2353 | keyval, hk, hv); 2354 | check_reply(sreply, context, 2355 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2356 | "could not add key %s", hk); 2357 | freeReplyObject(sreply); 2358 | } 2359 | } 2360 | break; 2361 | case PG_REDIS_ZSET_TABLE: 2362 | { 2363 | int i; 2364 | char ibuff[100]; 2365 | 2366 | for (i = 0; i < nitems; i++) 2367 | { 2368 | valueval = OutputFunctionCall(&fmstate->p_flinfo[1], 2369 | elements[i]); 2370 | sprintf(ibuff, "%d", i); 2371 | 2372 | /* 2373 | * score comes BEFORE value in ZADD, which seems 2374 | * slightly perverse 2375 | */ 2376 | sreply = redisCommand(context, "ZADD %s %s %s", 2377 | keyval, ibuff, valueval); 2378 | check_reply(sreply, context, 2379 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2380 | "could not add key %s", valueval); 2381 | freeReplyObject(sreply); 2382 | } 2383 | } 2384 | break; 2385 | default: 2386 | ereport(ERROR, 2387 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 2388 | errmsg("insert not supported for this type of table") 2389 | )); 2390 | } 2391 | 2392 | /* if it's a keyset organized table, add key to keyset using SADD */ 2393 | 2394 | if (fmstate->keyset) 2395 | { 2396 | sreply = redisCommand(context, "SADD %s %s", 2397 | fmstate->keyset, keyval); 2398 | check_reply(sreply, context, 2399 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2400 | "could not add keyset element %s", valueval); 2401 | freeReplyObject(sreply); 2402 | } 2403 | } 2404 | return slot; 2405 | } 2406 | 2407 | /* 2408 | * redisExecForeignDelete 2409 | * Delete one row from a foreign table 2410 | */ 2411 | static TupleTableSlot * 2412 | redisExecForeignDelete(EState *estate, 2413 | ResultRelInfo *rinfo, 2414 | TupleTableSlot *slot, 2415 | TupleTableSlot *planSlot) 2416 | { 2417 | RedisFdwModifyState *fmstate = 2418 | (RedisFdwModifyState *) rinfo->ri_FdwState; 2419 | redisContext *context = fmstate->context; 2420 | redisReply *reply = NULL; 2421 | bool isNull; 2422 | Datum datum; 2423 | char *keyval; 2424 | 2425 | #ifdef DEBUG 2426 | elog(NOTICE, "redisExecForeignDelete"); 2427 | #endif 2428 | 2429 | /* Get the key that was passed up as a resjunk column */ 2430 | datum = ExecGetJunkAttribute(planSlot, 2431 | fmstate->keyAttno, 2432 | &isNull); 2433 | 2434 | keyval = OutputFunctionCall(&fmstate->p_flinfo[0], datum); 2435 | 2436 | /* elog(NOTICE,"deleting keyval %s",keyval); */ 2437 | 2438 | if (fmstate->singleton_key) 2439 | { 2440 | switch (fmstate->table_type) 2441 | { 2442 | case PG_REDIS_SCALAR_TABLE: 2443 | reply = redisCommand(context, "DEL %s", 2444 | fmstate->singleton_key); 2445 | break; 2446 | case PG_REDIS_SET_TABLE: 2447 | reply = redisCommand(context, "SREM %s %s", 2448 | fmstate->singleton_key, keyval); 2449 | break; 2450 | case PG_REDIS_HASH_TABLE: 2451 | reply = redisCommand(context, "HDEL %s %s", 2452 | fmstate->singleton_key, keyval); 2453 | break; 2454 | case PG_REDIS_ZSET_TABLE: 2455 | reply = redisCommand(context, "ZREM %s %s", 2456 | fmstate->singleton_key, keyval); 2457 | break; 2458 | default: 2459 | /* Note: List table has already generated an error */ 2460 | ereport(ERROR, 2461 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 2462 | errmsg("delete not supported for this type of table") 2463 | )); 2464 | } 2465 | } 2466 | else /* not a singleton */ 2467 | { 2468 | /* use DEL regardless of table type */ 2469 | reply = redisCommand(context, "DEL %s", keyval); 2470 | } 2471 | 2472 | check_reply(reply, context, 2473 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2474 | "failed to delete key %s", keyval); 2475 | freeReplyObject(reply); 2476 | 2477 | if (fmstate->keyset) 2478 | { 2479 | reply = redisCommand(context, "SREM %s %s", 2480 | fmstate->keyset, keyval); 2481 | 2482 | check_reply(reply, context, 2483 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2484 | "failed to delete keyset element %s", keyval); 2485 | freeReplyObject(reply); 2486 | 2487 | } 2488 | 2489 | return slot; 2490 | } 2491 | 2492 | /* 2493 | * redisExecForeignUpdate 2494 | * Update one row in a foreign table 2495 | */ 2496 | static TupleTableSlot * 2497 | redisExecForeignUpdate(EState *estate, 2498 | ResultRelInfo *rinfo, 2499 | TupleTableSlot *slot, 2500 | TupleTableSlot *planSlot) 2501 | { 2502 | RedisFdwModifyState *fmstate = 2503 | (RedisFdwModifyState *) rinfo->ri_FdwState; 2504 | redisContext *context = fmstate->context; 2505 | redisReply *ereply = NULL; 2506 | Datum datum; 2507 | char *keyval; 2508 | char *newkey; 2509 | char *newval = NULL; 2510 | char **array_vals = NULL; 2511 | bool isNull; 2512 | ListCell *lc = NULL; 2513 | int flslot = 1; 2514 | int nitems = 0; 2515 | 2516 | #ifdef DEBUG 2517 | elog(NOTICE, "redisExecForeignUpdate"); 2518 | #endif 2519 | 2520 | /* Get the key that was passed up as a resjunk column */ 2521 | datum = ExecGetJunkAttribute(planSlot, 2522 | fmstate->keyAttno, 2523 | &isNull); 2524 | 2525 | keyval = OutputFunctionCall(&fmstate->p_flinfo[0], datum); 2526 | 2527 | newkey = keyval; 2528 | 2529 | Assert(keyval != NULL); 2530 | 2531 | /* extract the updated values */ 2532 | foreach(lc, fmstate->target_attrs) 2533 | { 2534 | int attnum = lfirst_int(lc); 2535 | 2536 | datum = slot_getattr(slot, attnum, &isNull); 2537 | 2538 | if (isNull) 2539 | elog(ERROR, "NULL update not supported"); 2540 | 2541 | if (attnum == 1) 2542 | { 2543 | newkey = OutputFunctionCall(&fmstate->p_flinfo[flslot], datum); 2544 | } 2545 | else if (fmstate->singleton_key || 2546 | fmstate->table_type == PG_REDIS_SCALAR_TABLE) 2547 | { 2548 | /* 2549 | * non-singleton scalar value, or singleton hash value, or 2550 | * singleton zset priority. 2551 | */ 2552 | newval = OutputFunctionCall(&fmstate->p_flinfo[flslot], datum); 2553 | } 2554 | else 2555 | { 2556 | /* 2557 | * must be a non-singleton non-scalar table. so it must be an 2558 | * array. 2559 | */ 2560 | int i; 2561 | Datum *elements; 2562 | bool *nulls; 2563 | int16 typlen; 2564 | bool typbyval; 2565 | char typalign; 2566 | 2567 | get_typlenbyvalalign(fmstate->array_elem_type, 2568 | &typlen, &typbyval, &typalign); 2569 | 2570 | deconstruct_array(DatumGetArrayTypeP(datum), 2571 | fmstate->array_elem_type, typlen, typbyval, 2572 | typalign, &elements, &nulls, 2573 | &nitems); 2574 | 2575 | if (nitems == 0) 2576 | ereport(ERROR, 2577 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 2578 | errmsg("cannot store empty list in a Redis table") 2579 | )); 2580 | 2581 | if (fmstate->table_type == PG_REDIS_HASH_TABLE && nitems % 2 != 0) 2582 | ereport(ERROR, 2583 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 2584 | errmsg("cannot decompose odd number of items into a Redis hash") 2585 | )); 2586 | 2587 | array_vals = palloc(nitems * sizeof(char *)); 2588 | 2589 | for (i = 0; i < nitems; i++) 2590 | { 2591 | if (nulls[i]) 2592 | ereport(ERROR, 2593 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 2594 | errmsg("cannot set NULL in a Redis table") 2595 | )); 2596 | 2597 | array_vals[i] = OutputFunctionCall(&fmstate->p_flinfo[flslot], 2598 | elements[i]); 2599 | } 2600 | } 2601 | 2602 | flslot++; 2603 | } 2604 | 2605 | /* now we have all the data we need */ 2606 | 2607 | /* if newkey = keyval then we're not updating the key */ 2608 | if (strcmp(keyval, newkey) != 0) 2609 | { 2610 | bool ok = true; 2611 | 2612 | ereply = NULL; 2613 | 2614 | /* make sure the new key doesn't exist */ 2615 | if (!fmstate->singleton_key) 2616 | { 2617 | ereply = redisCommand(context, "EXISTS %s", newkey); 2618 | ok = ereply->type == REDIS_REPLY_INTEGER && ereply->integer == 0; 2619 | check_reply(ereply, context, 2620 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2621 | "failed checking key existence %s", newkey); 2622 | } 2623 | else 2624 | { 2625 | switch (fmstate->table_type) 2626 | { 2627 | case PG_REDIS_SET_TABLE: 2628 | ereply = redisCommand(context, "SISMEMBER %s %s", 2629 | fmstate->singleton_key, newkey); 2630 | break; 2631 | case PG_REDIS_ZSET_TABLE: 2632 | ereply = redisCommand(context, "ZRANK %s %s", 2633 | fmstate->singleton_key, newkey); 2634 | break; 2635 | case PG_REDIS_HASH_TABLE: 2636 | ereply = redisCommand(context, "HEXISTS %s %s", 2637 | fmstate->singleton_key, newkey); 2638 | break; 2639 | default: 2640 | break; 2641 | } 2642 | if (fmstate->table_type != PG_REDIS_SCALAR_TABLE) 2643 | { 2644 | if (fmstate->table_type != PG_REDIS_ZSET_TABLE) 2645 | ok = ereply->type == REDIS_REPLY_INTEGER && 2646 | ereply->integer == 0; 2647 | else 2648 | ok = ereply->type == REDIS_REPLY_NIL; 2649 | 2650 | check_reply(ereply, context, 2651 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2652 | "failed checking key existence %s", keyval); 2653 | } 2654 | } 2655 | 2656 | if (ereply != NULL) 2657 | freeReplyObject(ereply); 2658 | 2659 | if (!ok) 2660 | ereport(ERROR, 2661 | (errcode(ERRCODE_UNIQUE_VIOLATION), 2662 | errmsg("key already exists: %s", newkey))); 2663 | 2664 | if (!fmstate->singleton_key) 2665 | { 2666 | if (fmstate->keyprefix && strncmp(fmstate->keyprefix, newkey, 2667 | strlen(fmstate->keyprefix)) != 0) 2668 | ereport(ERROR, 2669 | (errcode(ERRCODE_UNIQUE_VIOLATION), 2670 | errmsg("key prefix condition violation: %s", newkey))); 2671 | 2672 | ereply = redisCommand(context, "RENAME %s %s", keyval, newkey); 2673 | 2674 | check_reply(ereply, context, 2675 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2676 | "failure renaming key %s", keyval); 2677 | freeReplyObject(ereply); 2678 | 2679 | if (newval && fmstate->table_type == PG_REDIS_SCALAR_TABLE) 2680 | { 2681 | ereply = redisCommand(context, "SET %s %s", newkey, newval); 2682 | 2683 | check_reply(ereply, context, 2684 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2685 | "upating key %s", newkey); 2686 | freeReplyObject(ereply); 2687 | } 2688 | 2689 | if (fmstate->keyset) 2690 | { 2691 | ereply = redisCommand(context, "SREM %s %s", fmstate->keyset, 2692 | keyval); 2693 | check_reply(ereply, context, 2694 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2695 | "deleting keyset element %s", keyval); 2696 | freeReplyObject(ereply); 2697 | 2698 | ereply = redisCommand(context, "SADD %s %s", fmstate->keyset, 2699 | newkey); 2700 | 2701 | check_reply(ereply, context, 2702 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2703 | "adding keyset element %s", newkey); 2704 | freeReplyObject(ereply); } 2705 | } 2706 | else /* is a singleton */ 2707 | { 2708 | switch (fmstate->table_type) 2709 | { 2710 | case PG_REDIS_SCALAR_TABLE: 2711 | ereply = redisCommand(context, "SET %s %s", 2712 | fmstate->singleton_key, newkey); 2713 | check_reply(ereply, context, 2714 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2715 | "setting value %s", newkey); 2716 | freeReplyObject(ereply); 2717 | break; 2718 | case PG_REDIS_SET_TABLE: 2719 | ereply = redisCommand(context, "SREM %s %s", 2720 | fmstate->singleton_key, keyval); 2721 | check_reply(ereply, context, 2722 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2723 | "removing value %s", keyval); 2724 | freeReplyObject(ereply); 2725 | ereply = redisCommand(context, "SADD %s %s", 2726 | fmstate->singleton_key, newkey); 2727 | check_reply(ereply, context, 2728 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2729 | "setting value %s", newkey); 2730 | freeReplyObject(ereply); 2731 | break; 2732 | case PG_REDIS_ZSET_TABLE: 2733 | { 2734 | char *priority = newval; 2735 | 2736 | if (!priority) 2737 | { 2738 | ereply = redisCommand(context, "ZSCORE %s %s", 2739 | fmstate->singleton_key, 2740 | keyval); 2741 | check_reply(ereply, context, 2742 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2743 | "getting score for key %s", keyval); 2744 | priority = pstrdup(ereply->str); 2745 | freeReplyObject(ereply); 2746 | } 2747 | ereply = redisCommand(context, "ZREM %s %s", 2748 | fmstate->singleton_key, keyval); 2749 | check_reply(ereply, context, 2750 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2751 | "removing set element %s", keyval); 2752 | freeReplyObject(ereply); 2753 | 2754 | ereply = redisCommand(context, "ZADD %s %s %s", 2755 | fmstate->singleton_key, 2756 | priority, newkey); 2757 | check_reply(ereply, context, 2758 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2759 | "setting element %s", newkey); 2760 | freeReplyObject(ereply); 2761 | } 2762 | break; 2763 | case PG_REDIS_HASH_TABLE: 2764 | { 2765 | char *nval = newval; 2766 | 2767 | if (!nval) 2768 | { 2769 | ereply = redisCommand(context, "HGET %s %s", 2770 | fmstate->singleton_key, 2771 | keyval); 2772 | check_reply(ereply, context, 2773 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2774 | "fetching vcalue for key %s", keyval); 2775 | nval = pstrdup(ereply->str); 2776 | freeReplyObject(ereply); 2777 | } 2778 | ereply = redisCommand(context, "HDEL %s %s", 2779 | fmstate->singleton_key, keyval); 2780 | check_reply(ereply, context, 2781 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2782 | "removing hash element %s", keyval); 2783 | freeReplyObject(ereply); 2784 | 2785 | ereply = redisCommand(context, "HSET %s %s %s", 2786 | fmstate->singleton_key, newkey, 2787 | nval); 2788 | check_reply(ereply, context, 2789 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2790 | "adding hash element %s", newkey); 2791 | freeReplyObject(ereply); 2792 | } 2793 | break; 2794 | default: 2795 | break; 2796 | } 2797 | } 2798 | } /* no key update */ 2799 | else if (newval) 2800 | { 2801 | if (!fmstate->singleton_key) 2802 | { 2803 | Assert(fmstate->table_type == PG_REDIS_SCALAR_TABLE); 2804 | ereply = redisCommand(context, "SET %s %s", keyval, newval); 2805 | } 2806 | else 2807 | { 2808 | if (fmstate->table_type == PG_REDIS_ZSET_TABLE) 2809 | ereply = redisCommand(context, "ZADD %s %s %s", 2810 | fmstate->singleton_key, newval, keyval); 2811 | else if (fmstate->table_type == PG_REDIS_HASH_TABLE) 2812 | ereply = redisCommand(context, "HSET %s %s %s", 2813 | fmstate->singleton_key, keyval, newval); 2814 | else 2815 | elog(ERROR, "impossible update"); /* should not happen */ 2816 | } 2817 | 2818 | check_reply(ereply, context, 2819 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2820 | "setting key %s", keyval); 2821 | freeReplyObject(ereply); 2822 | } 2823 | 2824 | if (array_vals) 2825 | { 2826 | 2827 | Assert(!fmstate->singleton_key); 2828 | 2829 | ereply = redisCommand(context, "DEL %s ", newkey); 2830 | check_reply(ereply, context, 2831 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2832 | "could not delete key %s", newkey); 2833 | freeReplyObject(ereply); 2834 | 2835 | switch (fmstate->table_type) 2836 | { 2837 | case PG_REDIS_SET_TABLE: 2838 | { 2839 | int i; 2840 | 2841 | for (i = 0; i < nitems; i++) 2842 | { 2843 | ereply = redisCommand(context, "SADD %s %s", 2844 | newkey, array_vals[i]); 2845 | check_reply(ereply, context, 2846 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2847 | "could not add element %s", array_vals[i]); 2848 | freeReplyObject(ereply); 2849 | } 2850 | } 2851 | break; 2852 | case PG_REDIS_LIST_TABLE: 2853 | { 2854 | int i; 2855 | 2856 | for (i = 0; i < nitems; i++) 2857 | { 2858 | ereply = redisCommand(context, "RPUSH %s %s", 2859 | newkey, array_vals[i]); 2860 | check_reply(ereply, context, 2861 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2862 | "could not add value %s", array_vals[i]); 2863 | freeReplyObject(ereply); 2864 | } 2865 | } 2866 | break; 2867 | case PG_REDIS_HASH_TABLE: 2868 | { 2869 | int i; 2870 | char *hk, 2871 | *hv; 2872 | 2873 | for (i = 0; i < nitems; i += 2) 2874 | { 2875 | hk = array_vals[i]; 2876 | hv = array_vals[i + 1]; 2877 | ereply = redisCommand(context, "HSET %s %s %s", 2878 | newkey, hk, hv); 2879 | check_reply(ereply, context, 2880 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2881 | "could not add key %s", hk); 2882 | freeReplyObject(ereply); 2883 | } 2884 | } 2885 | break; 2886 | case PG_REDIS_ZSET_TABLE: 2887 | { 2888 | int i; 2889 | char ibuff[100]; 2890 | char *zval; 2891 | 2892 | for (i = 0; i < nitems; i++) 2893 | { 2894 | zval = array_vals[i]; 2895 | sprintf(ibuff, "%d", i); 2896 | 2897 | /* 2898 | * score comes BEFORE value in ZADD, which seems 2899 | * slightly perverse 2900 | */ 2901 | ereply = redisCommand(context, "ZADD %s %s %s", 2902 | newkey, ibuff, zval); 2903 | check_reply(ereply, context, 2904 | ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, 2905 | "could not add key %s", zval); 2906 | freeReplyObject(ereply); 2907 | } 2908 | } 2909 | break; 2910 | default: 2911 | ereport(ERROR, 2912 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 2913 | errmsg("update not supported for this type of table") 2914 | )); 2915 | } 2916 | 2917 | } 2918 | return slot; 2919 | } 2920 | 2921 | /* 2922 | * redisEndForeignModify 2923 | * Finish an insert/update/delete operation on a foreign table 2924 | */ 2925 | static void 2926 | redisEndForeignModify(EState *estate, 2927 | ResultRelInfo *rinfo) 2928 | { 2929 | RedisFdwModifyState *fmstate = (RedisFdwModifyState *) rinfo->ri_FdwState; 2930 | 2931 | #ifdef DEBUG 2932 | elog(NOTICE, "redisEndForeignScan"); 2933 | #endif 2934 | 2935 | /* if fmstate is NULL, we are in EXPLAIN; nothing to do */ 2936 | if (fmstate) 2937 | { 2938 | if (fmstate->context) 2939 | redisFree(fmstate->context); 2940 | } 2941 | } 2942 | -------------------------------------------------------------------------------- /redis_fdw.control: -------------------------------------------------------------------------------- 1 | ########################################################################## 2 | # 3 | # foreign-data wrapper for Redis 4 | # 5 | # Copyright (c) 2011, PostgreSQL Global Development Group 6 | # 7 | # This software is released under the PostgreSQL Licence 8 | # 9 | # Author: Dave Page 10 | # 11 | # IDENTIFICATION 12 | # redis_fdw/redis_fdw.control 13 | # 14 | ########################################################################## 15 | 16 | comment = 'Foreign data wrapper for querying a Redis server' 17 | default_version = '1.0' 18 | module_pathname = '$libdir/redis_fdw' 19 | relocatable = true 20 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | /results/ 2 | -------------------------------------------------------------------------------- /test/expected/redis_fdw.out: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION atsort( a text[]) 2 | RETURNS text[] 3 | LANGUAGE sql 4 | IMMUTABLE STRICT 5 | AS $function$ 6 | select array(select unnest($1) order by 1) 7 | $function$ 8 | ; 9 | create server localredis foreign data wrapper redis_fdw; 10 | create user mapping for public server localredis; 11 | -- tables for all 5 data types (4 structured plus scalar) 12 | create foreign table db15(key text, value text) 13 | server localredis 14 | options (database '15'); 15 | create foreign table db15_hash(key text, value text) 16 | server localredis 17 | options (database '15', tabletype 'hash'); 18 | create foreign table db15_set(key text, value text) 19 | server localredis 20 | options (database '15', tabletype 'set'); 21 | create foreign table db15_list(key text, value text) 22 | server localredis 23 | options (database '15', tabletype 'list'); 24 | create foreign table db15_zset(key text, value text) 25 | server localredis 26 | options (database '15', tabletype 'zset'); 27 | -- make sure they are all empty - if any are not stop the script right now 28 | \set ON_ERROR_STOP 29 | do $$ 30 | declare 31 | rows bigint; 32 | begin 33 | select into rows 34 | (select count(*) from db15) + 35 | (select count(*) from db15_hash) + 36 | (select count(*) from db15_set) + 37 | (select count(*) from db15_list) + 38 | (select count(*) from db15_zset); 39 | if rows > 0 40 | then 41 | raise EXCEPTION 'db 15 not empty'; 42 | end if; 43 | end; 44 | $$; 45 | \unset ON_ERROR_STOP 46 | -- ok, empty, so now run the setup script 47 | \! redis-cli < test/sql/redis_setup 48 | OK 49 | OK 50 | OK 51 | 8 52 | 5 53 | 6 54 | 4 55 | OK 56 | OK 57 | 6 58 | 6 59 | 2 60 | 2 61 | 2 62 | 2 63 | select * from db15 order by key; 64 | key | value 65 | -----+-------- 66 | baz | blurfl 67 | foo | bar 68 | (2 rows) 69 | 70 | select * from db15 where key = 'foo'; 71 | key | value 72 | -----+------- 73 | foo | bar 74 | (1 row) 75 | 76 | -- hash 77 | create foreign table db15_hash_prefix(key text, value text) 78 | server localredis 79 | options (tabletype 'hash', tablekeyprefix 'hash', database '15'); 80 | create foreign table db15_hash_prefix_array(key text, value text[]) 81 | server localredis 82 | options (tabletype 'hash', tablekeyprefix 'hash', database '15'); 83 | create foreign table db15_hash_keyset_array(key text, value text[]) 84 | server localredis 85 | options (tabletype 'hash', tablekeyset 'hkeys', database '15'); 86 | select * from db15_hash_prefix order by key; 87 | key | value 88 | -------+------------------------------------------- 89 | hash1 | {"k1","v1","k2","v2","k3","v3","k4","v4"} 90 | hash2 | {"k1","v5","k2","v6","k3","v7","k4","v8"} 91 | (2 rows) 92 | 93 | select * from db15_hash_prefix where key = 'hash1'; 94 | key | value 95 | -------+------------------------------------------- 96 | hash1 | {"k1","v1","k2","v2","k3","v3","k4","v4"} 97 | (1 row) 98 | 99 | select * from db15_hash_prefix_array order by key; 100 | key | value 101 | -------+--------------------------- 102 | hash1 | {k1,v1,k2,v2,k3,v3,k4,v4} 103 | hash2 | {k1,v5,k2,v6,k3,v7,k4,v8} 104 | (2 rows) 105 | 106 | select * from db15_hash_prefix_array where key = 'hash1'; 107 | key | value 108 | -------+--------------------------- 109 | hash1 | {k1,v1,k2,v2,k3,v3,k4,v4} 110 | (1 row) 111 | 112 | select * from db15_hash_keyset_array order by key; 113 | key | value 114 | -------+--------------------------- 115 | hash1 | {k1,v1,k2,v2,k3,v3,k4,v4} 116 | hash2 | {k1,v5,k2,v6,k3,v7,k4,v8} 117 | (2 rows) 118 | 119 | select * from db15_hash_keyset_array where key = 'hash1'; 120 | key | value 121 | -------+--------------------------- 122 | hash1 | {k1,v1,k2,v2,k3,v3,k4,v4} 123 | (1 row) 124 | 125 | -- a couple of nifty things we an do with hash tables 126 | select key, hstore(value) from db15_hash_prefix_array order by key; 127 | key | hstore 128 | -------+------------------------------------------------ 129 | hash1 | "k1"=>"v1", "k2"=>"v2", "k3"=>"v3", "k4"=>"v4" 130 | hash2 | "k1"=>"v5", "k2"=>"v6", "k3"=>"v7", "k4"=>"v8" 131 | (2 rows) 132 | 133 | create type atab as (k1 text, k2 text, k3 text); 134 | select key, (populate_record(null::atab, hstore(value))).* 135 | from db15_hash_prefix_array 136 | order by key; 137 | key | k1 | k2 | k3 138 | -------+----+----+---- 139 | hash1 | v1 | v2 | v3 140 | hash2 | v5 | v6 | v7 141 | (2 rows) 142 | 143 | -- set 144 | create foreign table db15_set_prefix(key text, value text) 145 | server localredis 146 | options (tabletype 'set', tablekeyprefix 'set', database '15'); 147 | create foreign table db15_set_prefix_array(key text, value text[]) 148 | server localredis 149 | options (tabletype 'set', tablekeyprefix 'set', database '15'); 150 | create foreign table db15_set_keyset_array(key text, value text[]) 151 | server localredis 152 | options (tabletype 'set', tablekeyset 'skeys', database '15'); 153 | -- need to use atsort() on set results to get predicable output 154 | -- since redis will give them back in arbitrary order 155 | -- means we can't show the actual value for db15_set_prefix which has it as a 156 | -- single text field 157 | select key, atsort(value::text[]) as value from db15_set_prefix order by key; 158 | key | value 159 | ------+--------------------------- 160 | set1 | {m1,m2,m3,m4,m5,m6,m7,m8} 161 | set2 | {m10,m11,m12,m8,m9} 162 | (2 rows) 163 | 164 | select key, atsort(value::text[]) as value from db15_set_prefix where key = 'set1'; 165 | key | value 166 | ------+--------------------------- 167 | set1 | {m1,m2,m3,m4,m5,m6,m7,m8} 168 | (1 row) 169 | 170 | select key, atsort(value) as value from db15_set_prefix_array order by key; 171 | key | value 172 | ------+--------------------------- 173 | set1 | {m1,m2,m3,m4,m5,m6,m7,m8} 174 | set2 | {m10,m11,m12,m8,m9} 175 | (2 rows) 176 | 177 | select key, atsort(value) as value from db15_set_prefix_array where key = 'set1'; 178 | key | value 179 | ------+--------------------------- 180 | set1 | {m1,m2,m3,m4,m5,m6,m7,m8} 181 | (1 row) 182 | 183 | select key, atsort(value) as value from db15_set_keyset_array order by key; 184 | key | value 185 | ------+--------------------------- 186 | set1 | {m1,m2,m3,m4,m5,m6,m7,m8} 187 | set2 | {m10,m11,m12,m8,m9} 188 | (2 rows) 189 | 190 | select key, atsort(value) as value from db15_set_keyset_array where key = 'set1'; 191 | key | value 192 | ------+--------------------------- 193 | set1 | {m1,m2,m3,m4,m5,m6,m7,m8} 194 | (1 row) 195 | 196 | -- list 197 | create foreign table db15_list_prefix(key text, value text) 198 | server localredis 199 | options (tabletype 'list', tablekeyprefix 'list', database '15'); 200 | create foreign table db15_list_prefix_array(key text, value text[]) 201 | server localredis 202 | options (tabletype 'list', tablekeyprefix 'list', database '15'); 203 | create foreign table db15_list_keyset_array(key text, value text[]) 204 | server localredis 205 | options (tabletype 'list', tablekeyset 'lkeys', database '15'); 206 | select * from db15_list_prefix order by key; 207 | key | value 208 | -------+--------------------------------- 209 | list1 | {"e6","e5","e4","e3","e2","e1"} 210 | list2 | {"e10","e9","e8","e7"} 211 | (2 rows) 212 | 213 | select * from db15_list_prefix where key = 'list1'; 214 | key | value 215 | -------+--------------------------------- 216 | list1 | {"e6","e5","e4","e3","e2","e1"} 217 | (1 row) 218 | 219 | select * from db15_list_prefix_array order by key; 220 | key | value 221 | -------+--------------------- 222 | list1 | {e6,e5,e4,e3,e2,e1} 223 | list2 | {e10,e9,e8,e7} 224 | (2 rows) 225 | 226 | select * from db15_list_prefix_array where key = 'list1'; 227 | key | value 228 | -------+--------------------- 229 | list1 | {e6,e5,e4,e3,e2,e1} 230 | (1 row) 231 | 232 | select * from db15_list_keyset_array order by key; 233 | key | value 234 | -------+--------------------- 235 | list1 | {e6,e5,e4,e3,e2,e1} 236 | list2 | {e10,e9,e8,e7} 237 | (2 rows) 238 | 239 | select * from db15_list_keyset_array where key = 'list1'; 240 | key | value 241 | -------+--------------------- 242 | list1 | {e6,e5,e4,e3,e2,e1} 243 | (1 row) 244 | 245 | -- zset 246 | create foreign table db15_zset_prefix(key text, value text) 247 | server localredis 248 | options (tabletype 'zset', tablekeyprefix 'zset', database '15'); 249 | create foreign table db15_zset_prefix_array(key text, value text[]) 250 | server localredis 251 | options (tabletype 'zset', tablekeyprefix 'zset', database '15'); 252 | create foreign table db15_zset_keyset_array(key text, value text[]) 253 | server localredis 254 | options (tabletype 'zset', tablekeyset 'zkeys', database '15'); 255 | select * from db15_zset_prefix order by key; 256 | key | value 257 | -------+------------------------------------ 258 | zset1 | {"z1","z2","z3","z4","z5","z6"} 259 | zset2 | {"z7","z8","z9","z10","z11","z12"} 260 | (2 rows) 261 | 262 | select * from db15_zset_prefix where key = 'zset1'; 263 | key | value 264 | -------+--------------------------------- 265 | zset1 | {"z1","z2","z3","z4","z5","z6"} 266 | (1 row) 267 | 268 | select * from db15_zset_prefix_array order by key; 269 | key | value 270 | -------+------------------------ 271 | zset1 | {z1,z2,z3,z4,z5,z6} 272 | zset2 | {z7,z8,z9,z10,z11,z12} 273 | (2 rows) 274 | 275 | select * from db15_zset_prefix_array where key = 'zset1'; 276 | key | value 277 | -------+--------------------- 278 | zset1 | {z1,z2,z3,z4,z5,z6} 279 | (1 row) 280 | 281 | select * from db15_zset_keyset_array order by key; 282 | key | value 283 | -------+------------------------ 284 | zset1 | {z1,z2,z3,z4,z5,z6} 285 | zset2 | {z7,z8,z9,z10,z11,z12} 286 | (2 rows) 287 | 288 | select * from db15_zset_keyset_array where key = 'zset1'; 289 | key | value 290 | -------+--------------------- 291 | zset1 | {z1,z2,z3,z4,z5,z6} 292 | (1 row) 293 | 294 | -- singleton scalar 295 | create foreign table db15_1key(value text) 296 | server localredis 297 | options (singleton_key 'foo', database '15'); 298 | select * from db15_1key; 299 | value 300 | ------- 301 | bar 302 | (1 row) 303 | 304 | -- singleton hash 305 | create foreign table db15_1key_hash(key text, value text) 306 | server localredis 307 | options (tabletype 'hash', singleton_key 'hash1', database '15'); 308 | select * from db15_1key_hash order by key; 309 | key | value 310 | -----+------- 311 | k1 | v1 312 | k2 | v2 313 | k3 | v3 314 | k4 | v4 315 | (4 rows) 316 | 317 | -- singleton set 318 | create foreign table db15_1key_set(value text) 319 | server localredis 320 | options (tabletype 'set', singleton_key 'set1', database '15'); 321 | select * from db15_1key_set order by value; 322 | value 323 | ------- 324 | m1 325 | m2 326 | m3 327 | m4 328 | m5 329 | m6 330 | m7 331 | m8 332 | (8 rows) 333 | 334 | -- singleton list 335 | create foreign table db15_1key_list(value text) 336 | server localredis 337 | options (tabletype 'list', singleton_key 'list1', database '15'); 338 | select * from db15_1key_list order by value; 339 | value 340 | ------- 341 | e1 342 | e2 343 | e3 344 | e4 345 | e5 346 | e6 347 | (6 rows) 348 | 349 | -- singleton zset 350 | create foreign table db15_1key_zset(value text) 351 | server localredis 352 | options (tabletype 'zset', singleton_key 'zset1', database '15'); 353 | select * from db15_1key_zset order by value; 354 | value 355 | ------- 356 | z1 357 | z2 358 | z3 359 | z4 360 | z5 361 | z6 362 | (6 rows) 363 | 364 | -- singleton zset with scores 365 | create foreign table db15_1key_zset_scores(value text, score numeric) 366 | server localredis 367 | options (tabletype 'zset', singleton_key 'zset1', database '15'); 368 | select * from db15_1key_zset_scores order by score desc; 369 | value | score 370 | -------+------- 371 | z6 | 6 372 | z5 | 5 373 | z4 | 4 374 | z3 | 3 375 | z2 | 2 376 | z1 | 1 377 | (6 rows) 378 | 379 | -- insert delete update 380 | -- first clean the database again 381 | \! redis-cli < test/sql/redis_clean 382 | OK 383 | OK 384 | -- singleton scalar table 385 | create foreign table db15_w_1key_scalar(val text) 386 | server localredis 387 | options (singleton_key 'w_1key_scalar', database '15'); 388 | select * from db15_w_1key_scalar; 389 | val 390 | ----- 391 | (0 rows) 392 | 393 | insert into db15_w_1key_scalar values ('only row'); 394 | select * from db15_w_1key_scalar; 395 | val 396 | ---------- 397 | only row 398 | (1 row) 399 | 400 | insert into db15_w_1key_scalar values ('only row'); 401 | ERROR: key already exists: w_1key_scalar 402 | delete from db15_w_1key_scalar where val = 'not only row'; 403 | select * from db15_w_1key_scalar; 404 | val 405 | ---------- 406 | only row 407 | (1 row) 408 | 409 | update db15_w_1key_scalar set val = 'new scalar val'; 410 | select * from db15_w_1key_scalar; 411 | val 412 | ---------------- 413 | new scalar val 414 | (1 row) 415 | 416 | delete from db15_w_1key_scalar; 417 | select * from db15_w_1key_scalar; 418 | val 419 | ----- 420 | (0 rows) 421 | 422 | -- singleton hash 423 | create foreign table db15_w_1key_hash(key text, val text) 424 | server localredis 425 | options (singleton_key 'w_1key_hash', tabletype 'hash', database '15'); 426 | select * from db15_w_1key_hash; 427 | key | val 428 | -----+----- 429 | (0 rows) 430 | 431 | insert into db15_w_1key_hash values ('a','b'), ('c','d'),('e','f'); 432 | select * from db15_w_1key_hash order by key; 433 | key | val 434 | -----+----- 435 | a | b 436 | c | d 437 | e | f 438 | (3 rows) 439 | 440 | insert into db15_w_1key_hash values ('a','b'); 441 | ERROR: key already exists: a 442 | delete from db15_w_1key_hash where key = 'a'; 443 | delete from db15_w_1key_hash where key = 'a'; 444 | select * from db15_w_1key_hash order by key; 445 | key | val 446 | -----+----- 447 | c | d 448 | e | f 449 | (2 rows) 450 | 451 | update db15_w_1key_hash set key = 'x', val = 'y' where key = 'c'; 452 | select * from db15_w_1key_hash order by key; 453 | key | val 454 | -----+----- 455 | e | f 456 | x | y 457 | (2 rows) 458 | 459 | update db15_w_1key_hash set val = 'z' where key = 'e'; 460 | select * from db15_w_1key_hash order by key; 461 | key | val 462 | -----+----- 463 | e | z 464 | x | y 465 | (2 rows) 466 | 467 | update db15_w_1key_hash set key = 'w' where key = 'e'; 468 | select * from db15_w_1key_hash order by key; 469 | key | val 470 | -----+----- 471 | w | z 472 | x | y 473 | (2 rows) 474 | 475 | -- singleton list 476 | create foreign table db15_w_1key_list(val text) 477 | server localredis 478 | options (singleton_key 'w_1key_list', tabletype 'list', database '15'); 479 | select * from db15_w_1key_list; 480 | val 481 | ----- 482 | (0 rows) 483 | 484 | insert into db15_w_1key_list values ('a'), ('c'),('e'); 485 | -- for lists the order should (must) be determinate 486 | select * from db15_w_1key_list /* order by val */ ; 487 | val 488 | ----- 489 | a 490 | c 491 | e 492 | (3 rows) 493 | 494 | delete from db15_w_1key_list where val = 'a'; 495 | ERROR: delete not supported for this type of table 496 | delete from db15_w_1key_list where val = 'z'; 497 | ERROR: delete not supported for this type of table 498 | insert into db15_w_1key_list values ('b'), ('d'),('f'),('a'); -- dups allowed here 499 | select * from db15_w_1key_list /* order by val */; 500 | val 501 | ----- 502 | a 503 | c 504 | e 505 | b 506 | d 507 | f 508 | a 509 | (7 rows) 510 | 511 | update db15_w_1key_list set val = 'y'; 512 | ERROR: update not supported for this type of table 513 | -- singleton set 514 | create foreign table db15_w_1key_set(key text) 515 | server localredis 516 | options (singleton_key 'w_1key_set', tabletype 'set', database '15'); 517 | select * from db15_w_1key_set; 518 | key 519 | ----- 520 | (0 rows) 521 | 522 | insert into db15_w_1key_set values ('a'), ('c'),('e'); 523 | select * from db15_w_1key_set order by key; 524 | key 525 | ----- 526 | a 527 | c 528 | e 529 | (3 rows) 530 | 531 | insert into db15_w_1key_set values ('a'); -- error - dup 532 | ERROR: key already exists: a 533 | delete from db15_w_1key_set where key = 'c'; 534 | select * from db15_w_1key_set order by key; 535 | key 536 | ----- 537 | a 538 | e 539 | (2 rows) 540 | 541 | update db15_w_1key_set set key = 'x' where key = 'e'; 542 | select * from db15_w_1key_set order by key; 543 | key 544 | ----- 545 | a 546 | x 547 | (2 rows) 548 | 549 | -- singleton zset with scores 550 | create foreign table db15_w_1key_zset(key text, priority numeric) 551 | server localredis 552 | options (singleton_key 'w_1key_zset', tabletype 'zset', database '15'); 553 | select * from db15_w_1key_zset; 554 | key | priority 555 | -----+---------- 556 | (0 rows) 557 | 558 | insert into db15_w_1key_zset values ('a',1), ('c',5),('e',-5), ('h',10); 559 | select * from db15_w_1key_zset order by priority; 560 | key | priority 561 | -----+---------- 562 | e | -5 563 | a | 1 564 | c | 5 565 | h | 10 566 | (4 rows) 567 | 568 | insert into db15_w_1key_zset values ('a',99); 569 | ERROR: key already exists: a 570 | delete from db15_w_1key_zset where key = 'a'; 571 | select * from db15_w_1key_zset order by priority; 572 | key | priority 573 | -----+---------- 574 | e | -5 575 | c | 5 576 | h | 10 577 | (3 rows) 578 | 579 | delete from db15_w_1key_zset where priority = '5'; 580 | select * from db15_w_1key_zset order by priority; 581 | key | priority 582 | -----+---------- 583 | e | -5 584 | h | 10 585 | (2 rows) 586 | 587 | update db15_w_1key_zset set key = 'x', priority = 99 where priority = '-5'; 588 | select * from db15_w_1key_zset order by priority; 589 | key | priority 590 | -----+---------- 591 | h | 10 592 | x | 99 593 | (2 rows) 594 | 595 | update db15_w_1key_zset set key = 'y' where key = 'h'; 596 | select * from db15_w_1key_zset order by priority; 597 | key | priority 598 | -----+---------- 599 | y | 10 600 | x | 99 601 | (2 rows) 602 | 603 | update db15_w_1key_zset set priority = 20 where key = 'y'; 604 | select * from db15_w_1key_zset order by priority; 605 | key | priority 606 | -----+---------- 607 | y | 20 608 | x | 99 609 | (2 rows) 610 | 611 | -- singleton zset no scores 612 | -- use set from last step 613 | delete from db15_w_1key_zset; 614 | insert into db15_w_1key_zset values ('e',-5); 615 | create foreign table db15_w_1key_zsetx(key text) 616 | server localredis 617 | options (singleton_key 'w_1key_zset', tabletype 'zset', database '15'); 618 | select * from db15_w_1key_zsetx; 619 | key 620 | ----- 621 | e 622 | (1 row) 623 | 624 | insert into db15_w_1key_zsetx values ('a'), ('c'),('e'); -- can't insert 625 | ERROR: operation not supported for singleton zset table without priorities column 626 | update db15_w_1key_zsetx set key = 'z' where key = 'e'; 627 | select * from db15_w_1key_zsetx order by key; 628 | key 629 | ----- 630 | z 631 | (1 row) 632 | 633 | delete from db15_w_1key_zsetx where key = 'z'; 634 | select * from db15_w_1key_zsetx order by key; 635 | key 636 | ----- 637 | (0 rows) 638 | 639 | -- non-singleton scalar table no prefix no keyset 640 | create foreign table db15_w_scalar(key text, val text) 641 | server localredis 642 | options (database '15'); 643 | select * from db15_w_scalar; 644 | key | val 645 | -----+----- 646 | (0 rows) 647 | 648 | insert into db15_w_scalar values ('a_ws','b'), ('c_ws','d'),('e_ws','f'); 649 | select * from db15_w_scalar order by key; 650 | key | val 651 | ------+----- 652 | a_ws | b 653 | c_ws | d 654 | e_ws | f 655 | (3 rows) 656 | 657 | delete from db15_w_scalar where key = 'a_ws'; 658 | select * from db15_w_scalar order by key; 659 | key | val 660 | ------+----- 661 | c_ws | d 662 | e_ws | f 663 | (2 rows) 664 | 665 | update db15_w_scalar set key = 'x_ws', val='y' where key = 'e_ws'; 666 | select * from db15_w_scalar order by key; 667 | key | val 668 | ------+----- 669 | c_ws | d 670 | x_ws | y 671 | (2 rows) 672 | 673 | update db15_w_scalar set key = 'z_ws' where key = 'c_ws'; 674 | select * from db15_w_scalar order by key; 675 | key | val 676 | ------+----- 677 | x_ws | y 678 | z_ws | d 679 | (2 rows) 680 | 681 | update db15_w_scalar set val = 'z' where key = 'z_ws'; 682 | select * from db15_w_scalar order by key; 683 | key | val 684 | ------+----- 685 | x_ws | y 686 | z_ws | z 687 | (2 rows) 688 | 689 | /* 690 | -- don't delete the whole namespace 691 | delete from db15_w_scalar; 692 | 693 | select * from db15_w_scalar; 694 | */ 695 | -- non-singleton scalar table keyprefix 696 | create foreign table db15_w_scalar_pfx(key text, val text) 697 | server localredis 698 | options (database '15', tablekeyprefix 'w_scalar_'); 699 | select * from db15_w_scalar_pfx; 700 | key | val 701 | -----+----- 702 | (0 rows) 703 | 704 | insert into db15_w_scalar_pfx values ('w_scalar_a','b'), ('w_scalar_c','d'),('w_scalar_e','f'); 705 | insert into db15_w_scalar_pfx values ('x','y'); -- prefix error 706 | ERROR: key 'x' does not match table key prefix 'w_scalar_' 707 | insert into db15_w_scalar_pfx values ('w_scalar_a','x'); -- dup error 708 | ERROR: key already exists: w_scalar_a 709 | select * from db15_w_scalar_pfx order by key; 710 | key | val 711 | ------------+----- 712 | w_scalar_a | b 713 | w_scalar_c | d 714 | w_scalar_e | f 715 | (3 rows) 716 | 717 | delete from db15_w_scalar_pfx where key = 'w_scalar_a'; 718 | select * from db15_w_scalar_pfx order by key; 719 | key | val 720 | ------------+----- 721 | w_scalar_c | d 722 | w_scalar_e | f 723 | (2 rows) 724 | 725 | update db15_w_scalar_pfx set key = 'x', val = 'y' where key = 'w_scalar_c'; -- prefix err 726 | ERROR: key prefix condition violation: x 727 | update db15_w_scalar_pfx set key = 'x' where key = 'w_scalar_c'; -- prefix err 728 | ERROR: key prefix condition violation: x 729 | update db15_w_scalar_pfx set key = 'w_scalar_x', val = 'y' where key = 'w_scalar_c'; 730 | select * from db15_w_scalar_pfx order by key; 731 | key | val 732 | ------------+----- 733 | w_scalar_e | f 734 | w_scalar_x | y 735 | (2 rows) 736 | 737 | update db15_w_scalar_pfx set key = 'w_scalar_z' where key = 'w_scalar_x'; 738 | select * from db15_w_scalar_pfx order by key; 739 | key | val 740 | ------------+----- 741 | w_scalar_e | f 742 | w_scalar_z | y 743 | (2 rows) 744 | 745 | update db15_w_scalar_pfx set val = 'w' where key = 'w_scalar_e'; 746 | select * from db15_w_scalar_pfx order by key; 747 | key | val 748 | ------------+----- 749 | w_scalar_e | w 750 | w_scalar_z | y 751 | (2 rows) 752 | 753 | delete from db15_w_scalar_pfx; 754 | select * from db15_w_scalar_pfx order by key; 755 | key | val 756 | -----+----- 757 | (0 rows) 758 | 759 | -- non-singleton scalar table keyset 760 | create foreign table db15_w_scalar_kset(key text, val text) 761 | server localredis 762 | options (database '15', tablekeyset 'w_scalar_kset'); 763 | select * from db15_w_scalar_kset order by key; 764 | key | val 765 | -----+----- 766 | (0 rows) 767 | 768 | insert into db15_w_scalar_kset values ('a_wsks','b'), ('c_wsks','d'),('e_wsks','f'); 769 | insert into db15_w_scalar_kset values ('a_wsks','x'); -- dup error 770 | ERROR: key already exists: a_wsks 771 | select * from db15_w_scalar_kset order by key; 772 | key | val 773 | --------+----- 774 | a_wsks | b 775 | c_wsks | d 776 | e_wsks | f 777 | (3 rows) 778 | 779 | delete from db15_w_scalar_kset where key = 'a_wsks'; 780 | select * from db15_w_scalar_kset order by key; 781 | key | val 782 | --------+----- 783 | c_wsks | d 784 | e_wsks | f 785 | (2 rows) 786 | 787 | update db15_w_scalar_kset set key = 'x_wsks', val = 'y' where key = 'c_wsks'; 788 | select * from db15_w_scalar_kset order by key; 789 | key | val 790 | --------+----- 791 | e_wsks | f 792 | x_wsks | y 793 | (2 rows) 794 | 795 | update db15_w_scalar_kset set key = 'z_wsks' where key = 'x_wsks'; 796 | select * from db15_w_scalar_kset order by key; 797 | key | val 798 | --------+----- 799 | e_wsks | f 800 | z_wsks | y 801 | (2 rows) 802 | 803 | update db15_w_scalar_kset set val = 'w' where key = 'e_wsks'; 804 | select * from db15_w_scalar_kset order by key; 805 | key | val 806 | --------+----- 807 | e_wsks | w 808 | z_wsks | y 809 | (2 rows) 810 | 811 | delete from db15_w_scalar_kset; 812 | select * from db15_w_scalar_kset order by key; 813 | key | val 814 | -----+----- 815 | (0 rows) 816 | 817 | -- non-singleton set table no prefix no keyset 818 | -- non-array case -- fails 819 | create foreign table db15_w_set_nonarr(key text, val text) 820 | server localredis 821 | options (database '15', tabletype 'set'); 822 | insert into db15_w_set_nonarr values ('nkseta','{b,c,d}'), ('nksetc','{d,e,f}'),('nksete','{f,g,h}'); 823 | ERROR: cannot insert into this type of Redis table - needs an array 824 | /* 825 | -- namespace too polluted for this case 826 | create foreign table db15_w_set(key text, val text[]) 827 | server localredis 828 | options (database '15', tabletype 'set'); 829 | 830 | select * from db15_w_set; 831 | 832 | insert into db15_w_set values ('nkseta','{b,c,d}'), ('nksetc','{d,e,f}'),('nksete','{f,g,h}'); 833 | 834 | select * from db15_w_set; 835 | 836 | delete from db15_w_set where key = 'nkseta'; 837 | 838 | select * from db15_w_set; 839 | 840 | delete from db15_w_set; 841 | 842 | select * from db15_w_set; 843 | 844 | */ 845 | -- non-singleton set table keyprefix 846 | create foreign table db15_w_set_pfx(key text, val text[]) 847 | server localredis 848 | options (database '15', tabletype 'set', tablekeyprefix 'w_set_'); 849 | select * from db15_w_set_pfx; 850 | key | val 851 | -----+----- 852 | (0 rows) 853 | 854 | insert into db15_w_set_pfx values ('w_set_a','{b,c,d}'), ('w_set_c','{d,e,f}'),('w_set_e','{f,g,h}'); 855 | insert into db15_w_set_pfx values ('x','{y}'); -- prefix error 856 | ERROR: key 'x' does not match table key prefix 'w_set_' 857 | insert into db15_w_set_pfx values ('w_set_a','{x,y,z}'); -- dup error 858 | ERROR: key already exists: w_set_a 859 | select key, atsort(val) as val from db15_w_set_pfx order by key; 860 | key | val 861 | ---------+--------- 862 | w_set_a | {b,c,d} 863 | w_set_c | {d,e,f} 864 | w_set_e | {f,g,h} 865 | (3 rows) 866 | 867 | delete from db15_w_set_pfx where key = 'w_set_a'; 868 | select key, atsort(val) as val from db15_w_set_pfx order by key; 869 | key | val 870 | ---------+--------- 871 | w_set_c | {d,e,f} 872 | w_set_e | {f,g,h} 873 | (2 rows) 874 | 875 | update db15_w_set_pfx set key = 'x' where key = 'w_set_c'; -- prefix err 876 | ERROR: key prefix condition violation: x 877 | update db15_w_set_pfx set key = 'x', val = '{y}' where key = 'w_set_c'; -- prefix err 878 | ERROR: key prefix condition violation: x 879 | update db15_w_set_pfx set key = 'w_set_x', val = '{x,y,z}' where key = 'w_set_c'; 880 | select key, atsort(val) as val from db15_w_set_pfx order by key; 881 | key | val 882 | ---------+--------- 883 | w_set_e | {f,g,h} 884 | w_set_x | {x,y,z} 885 | (2 rows) 886 | 887 | update db15_w_set_pfx set key = 'w_set_z' where key = 'w_set_x'; 888 | select key, atsort(val) as val from db15_w_set_pfx order by key; 889 | key | val 890 | ---------+--------- 891 | w_set_e | {f,g,h} 892 | w_set_z | {x,y,z} 893 | (2 rows) 894 | 895 | update db15_w_set_pfx set val = '{q,r,s}' where key = 'w_set_e'; 896 | select key, atsort(val) as val from db15_w_set_pfx order by key; 897 | key | val 898 | ---------+--------- 899 | w_set_e | {q,r,s} 900 | w_set_z | {x,y,z} 901 | (2 rows) 902 | 903 | delete from db15_w_set_pfx; 904 | select key, atsort(val) as val from db15_w_set_pfx order by key; 905 | key | val 906 | -----+----- 907 | (0 rows) 908 | 909 | -- non-singleton set table keyset 910 | create foreign table db15_w_set_kset(key text, val text[]) 911 | server localredis 912 | options (database '15', tabletype 'set', tablekeyset 'w_set_kset'); 913 | select * from db15_w_set_kset; 914 | key | val 915 | -----+----- 916 | (0 rows) 917 | 918 | insert into db15_w_set_kset values ('a_wsk','{b,c,d}'), ('c_wsk','{d,e,f}'),('e_wsk','{f,g,h}'); 919 | insert into db15_w_set_kset values ('a_wsk','{x}'); -- dup error 920 | ERROR: key already exists: a_wsk 921 | select key, atsort(val) as val from db15_w_set_kset order by key; 922 | key | val 923 | -------+--------- 924 | a_wsk | {b,c,d} 925 | c_wsk | {d,e,f} 926 | e_wsk | {f,g,h} 927 | (3 rows) 928 | 929 | delete from db15_w_set_kset where key = 'a_wsk'; 930 | select key, atsort(val) as val from db15_w_set_kset order by key; 931 | key | val 932 | -------+--------- 933 | c_wsk | {d,e,f} 934 | e_wsk | {f,g,h} 935 | (2 rows) 936 | 937 | update db15_w_set_kset set key = 'x_wsk', val = '{x,y,z}' where key = 'c_wsk'; 938 | select key, atsort(val) as val from db15_w_set_kset order by key; 939 | key | val 940 | -------+--------- 941 | e_wsk | {f,g,h} 942 | x_wsk | {x,y,z} 943 | (2 rows) 944 | 945 | update db15_w_set_kset set key = 'z_wsk' where key = 'x_wsk'; 946 | select key, atsort(val) as val from db15_w_set_kset order by key; 947 | key | val 948 | -------+--------- 949 | e_wsk | {f,g,h} 950 | z_wsk | {x,y,z} 951 | (2 rows) 952 | 953 | update db15_w_set_kset set val = '{q,r,s}' where key = 'e_wsk'; 954 | select key, atsort(val) as val from db15_w_set_kset order by key; 955 | key | val 956 | -------+--------- 957 | e_wsk | {q,r,s} 958 | z_wsk | {x,y,z} 959 | (2 rows) 960 | 961 | delete from db15_w_set_kset; 962 | select * from db15_w_set_kset; 963 | key | val 964 | -----+----- 965 | (0 rows) 966 | 967 | -- non-singleton list table keyprefix 968 | create foreign table db15_w_list_pfx(key text, val text[]) 969 | server localredis 970 | options (database '15', tabletype 'list', tablekeyprefix 'w_list_'); 971 | select * from db15_w_list_pfx; 972 | key | val 973 | -----+----- 974 | (0 rows) 975 | 976 | insert into db15_w_list_pfx values ('w_list_a','{b,c,d}'), ('w_list_c','{d,e,f}'),('w_list_e','{f,g,h}'); 977 | insert into db15_w_list_pfx values ('x','{y}'); -- prefix error 978 | ERROR: key 'x' does not match table key prefix 'w_list_' 979 | insert into db15_w_list_pfx values ('w_list_a','{x,y,z}'); -- dup error 980 | ERROR: key already exists: w_list_a 981 | select * from db15_w_list_pfx order by key; 982 | key | val 983 | ----------+--------- 984 | w_list_a | {b,c,d} 985 | w_list_c | {d,e,f} 986 | w_list_e | {f,g,h} 987 | (3 rows) 988 | 989 | delete from db15_w_list_pfx where key = 'w_list_a'; 990 | select * from db15_w_list_pfx order by key; 991 | key | val 992 | ----------+--------- 993 | w_list_c | {d,e,f} 994 | w_list_e | {f,g,h} 995 | (2 rows) 996 | 997 | update db15_w_list_pfx set key = 'x' where key = 'w_list_c'; -- prefix err 998 | ERROR: key prefix condition violation: x 999 | update db15_w_list_pfx set key = 'x', val = '{y}' where key = 'w_list_c'; -- prefix err 1000 | ERROR: key prefix condition violation: x 1001 | update db15_w_list_pfx set key = 'w_list_x', val = '{x,y,z}' where key = 'w_list_c'; 1002 | select key, atsort(val) as val from db15_w_list_pfx order by key; 1003 | key | val 1004 | ----------+--------- 1005 | w_list_e | {f,g,h} 1006 | w_list_x | {x,y,z} 1007 | (2 rows) 1008 | 1009 | update db15_w_list_pfx set key = 'w_list_z' where key = 'w_list_x'; 1010 | select key, atsort(val) as val from db15_w_list_pfx order by key; 1011 | key | val 1012 | ----------+--------- 1013 | w_list_e | {f,g,h} 1014 | w_list_z | {x,y,z} 1015 | (2 rows) 1016 | 1017 | update db15_w_list_pfx set val = '{q,r,s}' where key = 'w_list_e'; 1018 | select key, atsort(val) as val from db15_w_list_pfx order by key; 1019 | key | val 1020 | ----------+--------- 1021 | w_list_e | {q,r,s} 1022 | w_list_z | {x,y,z} 1023 | (2 rows) 1024 | 1025 | delete from db15_w_list_pfx; 1026 | select * from db15_w_list_pfx; 1027 | key | val 1028 | -----+----- 1029 | (0 rows) 1030 | 1031 | -- non-singleton list table keyset 1032 | create foreign table db15_w_list_kset(key text, val text[]) 1033 | server localredis 1034 | options (database '15', tabletype 'list', tablekeyset 'w_list_kset'); 1035 | select * from db15_w_list_kset; 1036 | key | val 1037 | -----+----- 1038 | (0 rows) 1039 | 1040 | insert into db15_w_list_kset values ('a_wlk','{b,c,d}'), ('c_wlk','{d,e,f}'),('e_wlk','{f,g,h}'); 1041 | insert into db15_w_list_kset values ('a_wlk','{x}'); -- dup error 1042 | ERROR: key already exists: a_wlk 1043 | select * from db15_w_list_kset order by key; 1044 | key | val 1045 | -------+--------- 1046 | a_wlk | {b,c,d} 1047 | c_wlk | {d,e,f} 1048 | e_wlk | {f,g,h} 1049 | (3 rows) 1050 | 1051 | delete from db15_w_list_kset where key = 'a_wlk'; 1052 | select * from db15_w_list_kset order by key; 1053 | key | val 1054 | -------+--------- 1055 | c_wlk | {d,e,f} 1056 | e_wlk | {f,g,h} 1057 | (2 rows) 1058 | 1059 | update db15_w_list_kset set key = 'x_wlk', val = '{x,y,z}' where key = 'c_wlk'; 1060 | select key, atsort(val) as val from db15_w_list_kset order by key; 1061 | key | val 1062 | -------+--------- 1063 | e_wlk | {f,g,h} 1064 | x_wlk | {x,y,z} 1065 | (2 rows) 1066 | 1067 | update db15_w_list_kset set key = 'z_wlk' where key = 'x_wlk'; 1068 | select key, atsort(val) as val from db15_w_list_kset order by key; 1069 | key | val 1070 | -------+--------- 1071 | e_wlk | {f,g,h} 1072 | z_wlk | {x,y,z} 1073 | (2 rows) 1074 | 1075 | update db15_w_list_kset set val = '{q,r,s}' where key = 'e_wlk'; 1076 | select key, atsort(val) as val from db15_w_list_kset order by key; 1077 | key | val 1078 | -------+--------- 1079 | e_wlk | {q,r,s} 1080 | z_wlk | {x,y,z} 1081 | (2 rows) 1082 | 1083 | delete from db15_w_list_kset; 1084 | select * from db15_w_list_kset; 1085 | key | val 1086 | -----+----- 1087 | (0 rows) 1088 | 1089 | -- non-singleton zset table keyprefix 1090 | create foreign table db15_w_zset_pfx(key text, val text[]) 1091 | server localredis 1092 | options (database '15', tabletype 'zset', tablekeyprefix 'w_zset_'); 1093 | select * from db15_w_zset_pfx; 1094 | key | val 1095 | -----+----- 1096 | (0 rows) 1097 | 1098 | insert into db15_w_zset_pfx values ('w_zset_a','{b,c,d}'), ('w_zset_c','{d,e,f}'),('w_zset_e','{f,g,h}'); 1099 | insert into db15_w_zset_pfx values ('x','{y}'); -- prefix error 1100 | ERROR: key 'x' does not match table key prefix 'w_zset_' 1101 | insert into db15_w_zset_pfx values ('w_zset_a','{x,y,z}'); -- dup error 1102 | ERROR: key already exists: w_zset_a 1103 | select * from db15_w_zset_pfx order by key; 1104 | key | val 1105 | ----------+--------- 1106 | w_zset_a | {b,c,d} 1107 | w_zset_c | {d,e,f} 1108 | w_zset_e | {f,g,h} 1109 | (3 rows) 1110 | 1111 | delete from db15_w_zset_pfx where key = 'w_zset_a'; 1112 | select * from db15_w_zset_pfx order by key; 1113 | key | val 1114 | ----------+--------- 1115 | w_zset_c | {d,e,f} 1116 | w_zset_e | {f,g,h} 1117 | (2 rows) 1118 | 1119 | update db15_w_zset_pfx set key = 'x' where key = 'w_zset_c'; -- prefix err 1120 | ERROR: key prefix condition violation: x 1121 | update db15_w_zset_pfx set key = 'x', val = '{y}' where key = 'w_zset_c'; -- prefix err 1122 | ERROR: key prefix condition violation: x 1123 | update db15_w_zset_pfx set key = 'w_zset_x', val = '{x,y,z}' where key = 'w_zset_c'; 1124 | select key, atsort(val) as val from db15_w_zset_pfx order by key; 1125 | key | val 1126 | ----------+--------- 1127 | w_zset_e | {f,g,h} 1128 | w_zset_x | {x,y,z} 1129 | (2 rows) 1130 | 1131 | update db15_w_zset_pfx set key = 'w_zset_z' where key = 'w_zset_x'; 1132 | select key, atsort(val) as val from db15_w_zset_pfx order by key; 1133 | key | val 1134 | ----------+--------- 1135 | w_zset_e | {f,g,h} 1136 | w_zset_z | {x,y,z} 1137 | (2 rows) 1138 | 1139 | update db15_w_zset_pfx set val = '{q,r,s}' where key = 'w_zset_e'; 1140 | select key, atsort(val) as val from db15_w_zset_pfx order by key; 1141 | key | val 1142 | ----------+--------- 1143 | w_zset_e | {q,r,s} 1144 | w_zset_z | {x,y,z} 1145 | (2 rows) 1146 | 1147 | delete from db15_w_zset_pfx; 1148 | select * from db15_w_zset_pfx; 1149 | key | val 1150 | -----+----- 1151 | (0 rows) 1152 | 1153 | -- non-singleton zset table keyset 1154 | create foreign table db15_w_zset_kset(key text, val text[]) 1155 | server localredis 1156 | options (database '15', tabletype 'zset', tablekeyset 'w_zset_kset'); 1157 | select * from db15_w_zset_kset; 1158 | key | val 1159 | -----+----- 1160 | (0 rows) 1161 | 1162 | insert into db15_w_zset_kset values ('a_wzk','{b,c,d}'), ('c_wzk','{d,e,f}'),('e_wzk','{f,g,h}'); 1163 | insert into db15_w_zset_kset values ('a_wzk','{x}'); -- dup error 1164 | ERROR: key already exists: a_wzk 1165 | select * from db15_w_zset_kset order by key; 1166 | key | val 1167 | -------+--------- 1168 | a_wzk | {b,c,d} 1169 | c_wzk | {d,e,f} 1170 | e_wzk | {f,g,h} 1171 | (3 rows) 1172 | 1173 | delete from db15_w_zset_kset where key = 'a_wzk'; 1174 | select * from db15_w_zset_kset order by key; 1175 | key | val 1176 | -------+--------- 1177 | c_wzk | {d,e,f} 1178 | e_wzk | {f,g,h} 1179 | (2 rows) 1180 | 1181 | update db15_w_zset_kset set key = 'x_wlk', val = '{x,y,z}' where key = 'c_wzk'; 1182 | select key, atsort(val) as val from db15_w_zset_kset order by key; 1183 | key | val 1184 | -------+--------- 1185 | e_wzk | {f,g,h} 1186 | x_wlk | {x,y,z} 1187 | (2 rows) 1188 | 1189 | update db15_w_zset_kset set key = 'z_wzk' where key = 'x_wzk'; 1190 | select key, atsort(val) as val from db15_w_zset_kset order by key; 1191 | key | val 1192 | -------+--------- 1193 | e_wzk | {f,g,h} 1194 | x_wlk | {x,y,z} 1195 | (2 rows) 1196 | 1197 | update db15_w_zset_kset set val = '{q,r,s}' where key = 'e_wzk'; 1198 | select key, atsort(val) as val from db15_w_zset_kset order by key; 1199 | key | val 1200 | -------+--------- 1201 | e_wzk | {q,r,s} 1202 | x_wlk | {x,y,z} 1203 | (2 rows) 1204 | 1205 | delete from db15_w_zset_kset; 1206 | select * from db15_w_zset_kset; 1207 | key | val 1208 | -----+----- 1209 | (0 rows) 1210 | 1211 | -- non-singleton hash table prefix 1212 | create foreign table db15_w_hash_pfx(key text, val text[]) 1213 | server localredis 1214 | options (database '15', tabletype 'hash', tablekeyprefix 'w_hash_'); 1215 | select * from db15_w_hash_pfx; 1216 | key | val 1217 | -----+----- 1218 | (0 rows) 1219 | 1220 | insert into db15_w_hash_pfx values ('w_hash_e','{f,g,h}'); -- error 1221 | ERROR: cannot decompose odd number of items into a Redis hash 1222 | insert into db15_w_hash_pfx values ('w_hash_e','{}'); -- error 1223 | ERROR: cannot store empty list in a Redis table 1224 | insert into db15_w_hash_pfx values ('w_hash_a','{b,c,d,e}'), ('w_hash_c','{f,g,h,i}'),('w_hash_e','{j,k}'); 1225 | insert into db15_w_hash_pfx values ('x','{y,z}'); -- prefix error 1226 | ERROR: key 'x' does not match table key prefix 'w_hash_' 1227 | insert into db15_w_hash_pfx values ('w_hash_a','{y,z}'); -- dup error 1228 | ERROR: key already exists: w_hash_a 1229 | select * from db15_w_hash_pfx order by key; 1230 | key | val 1231 | ----------+----------- 1232 | w_hash_a | {b,c,d,e} 1233 | w_hash_c | {f,g,h,i} 1234 | w_hash_e | {j,k} 1235 | (3 rows) 1236 | 1237 | delete from db15_w_hash_pfx where key = 'w_hash_a'; 1238 | select * from db15_w_hash_pfx order by key; 1239 | key | val 1240 | ----------+----------- 1241 | w_hash_c | {f,g,h,i} 1242 | w_hash_e | {j,k} 1243 | (2 rows) 1244 | 1245 | update db15_w_hash_pfx set key = 'x' where key = 'w_hash_c'; -- prefix err 1246 | ERROR: key prefix condition violation: x 1247 | update db15_w_hash_pfx set key = 'x', val = '{y,z}' where key = 'w_hash_c'; -- prefix err 1248 | ERROR: key prefix condition violation: x 1249 | update db15_w_hash_pfx set key = 'w_hash_x', val = '{x,y,z}' where key = 'w_hash_c'; -- err 1250 | ERROR: cannot decompose odd number of items into a Redis hash 1251 | update db15_w_hash_pfx set key = 'w_hash_x', val = '{w,x,y,z}' where key = 'w_hash_c'; 1252 | select key, val from db15_w_hash_pfx order by key; 1253 | key | val 1254 | ----------+----------- 1255 | w_hash_e | {j,k} 1256 | w_hash_x | {w,x,y,z} 1257 | (2 rows) 1258 | 1259 | update db15_w_hash_pfx set key = 'w_hash_z' where key = 'w_hash_x'; 1260 | select key, val from db15_w_hash_pfx order by key; 1261 | key | val 1262 | ----------+----------- 1263 | w_hash_e | {j,k} 1264 | w_hash_z | {w,x,y,z} 1265 | (2 rows) 1266 | 1267 | update db15_w_hash_pfx set val = '{q,r,s}' where key = 'w_hash_e'; 1268 | ERROR: cannot decompose odd number of items into a Redis hash 1269 | select key, val from db15_w_hash_pfx order by key; 1270 | key | val 1271 | ----------+----------- 1272 | w_hash_e | {j,k} 1273 | w_hash_z | {w,x,y,z} 1274 | (2 rows) 1275 | 1276 | delete from db15_w_hash_pfx; 1277 | select * from db15_w_hash_pfx; 1278 | key | val 1279 | -----+----- 1280 | (0 rows) 1281 | 1282 | --non-singleton hash table keyset 1283 | create foreign table db15_w_hash_kset(key text, val text[]) 1284 | server localredis 1285 | options (database '15', tabletype 'hash', tablekeyset 'w_hash_kset'); 1286 | select * from db15_w_hash_kset; 1287 | key | val 1288 | -----+----- 1289 | (0 rows) 1290 | 1291 | insert into db15_w_hash_pfx values ('e_whk','{f,g,h}'); -- error 1292 | ERROR: key 'e_whk' does not match table key prefix 'w_hash_' 1293 | insert into db15_w_hash_pfx values ('e_whk','{}'); -- error 1294 | ERROR: key 'e_whk' does not match table key prefix 'w_hash_' 1295 | insert into db15_w_hash_kset values ('a_whk','{b,c,d,e}'), ('c_whk','{f,g,h,i}'),('e_whk','{j,k}'); 1296 | insert into db15_w_hash_kset values ('a_whk','{x,y}'); -- dup error 1297 | ERROR: key already exists: a_whk 1298 | select * from db15_w_hash_kset order by key; 1299 | key | val 1300 | -------+----------- 1301 | a_whk | {b,c,d,e} 1302 | c_whk | {f,g,h,i} 1303 | e_whk | {j,k} 1304 | (3 rows) 1305 | 1306 | delete from db15_w_hash_kset where key = 'a_whk'; 1307 | select * from db15_w_hash_kset order by key; 1308 | key | val 1309 | -------+----------- 1310 | c_whk | {f,g,h,i} 1311 | e_whk | {j,k} 1312 | (2 rows) 1313 | 1314 | update db15_w_hash_kset set key = 'x_whk', val = '{w,x,y,z}' where key = 'c_whk'; 1315 | select key, val from db15_w_hash_kset order by key; 1316 | key | val 1317 | -------+----------- 1318 | e_whk | {j,k} 1319 | x_whk | {w,x,y,z} 1320 | (2 rows) 1321 | 1322 | update db15_w_hash_kset set key = 'z_whk' where key = 'x_whk'; 1323 | select key, val from db15_w_hash_kset order by key; 1324 | key | val 1325 | -------+----------- 1326 | e_whk | {j,k} 1327 | z_whk | {w,x,y,z} 1328 | (2 rows) 1329 | 1330 | update db15_w_hash_kset set val = '{q,r}' where key = 'e_whk'; 1331 | select key, val from db15_w_hash_kset order by key; 1332 | key | val 1333 | -------+----------- 1334 | e_whk | {q,r} 1335 | z_whk | {w,x,y,z} 1336 | (2 rows) 1337 | 1338 | delete from db15_w_hash_kset; 1339 | select * from db15_w_hash_kset; 1340 | key | val 1341 | -----+----- 1342 | (0 rows) 1343 | 1344 | -- now clean up for the cursor tests 1345 | \! redis-cli < test/sql/redis_clean 1346 | OK 1347 | OK 1348 | -- cursor tests 1349 | create foreign table db15bigprefixscalar ( 1350 | key text not null, 1351 | val text 1352 | ) 1353 | server localredis 1354 | options (database '15', tablekeyprefix 'w_scalar_'); 1355 | create foreign table db15bigkeysetscalar ( 1356 | key text not null, 1357 | val text 1358 | ) 1359 | server localredis 1360 | options (database '15', tablekeyset 'w_kset'); 1361 | insert into db15 1362 | select 'junk' || x, 'junk' 1363 | from generate_series(1,10000) as x; 1364 | insert into db15bigprefixscalar 1365 | select 'w_scalar_' || x::text, 'val ' || x::text 1366 | from generate_series (1,10000) as x; 1367 | insert into db15bigkeysetscalar 1368 | select 'key_' || x::text, 'val ' || x::text 1369 | from generate_series (1,10000) as x; 1370 | insert into db15 1371 | select 'junk' || x, 'junk' 1372 | from generate_series(10001, 20000) as x; 1373 | insert into db15bigprefixscalar 1374 | select 'w_scalar_' || x::text, 'val ' || x::text 1375 | from generate_series (10001, 20000) as x; 1376 | insert into db15bigkeysetscalar 1377 | select 'key_' || x::text, 'val ' || x::text 1378 | from generate_series (10001, 20000) as x; 1379 | select count(*) from db15; 1380 | count 1381 | ------- 1382 | 60000 1383 | (1 row) 1384 | 1385 | select count(*) from db15bigprefixscalar; 1386 | count 1387 | ------- 1388 | 20000 1389 | (1 row) 1390 | 1391 | select count(*) from db15bigkeysetscalar; 1392 | count 1393 | ------- 1394 | 20000 1395 | (1 row) 1396 | 1397 | -- all done, so now blow everything in the db away again 1398 | \! redis-cli < test/sql/redis_clean 1399 | OK 1400 | OK 1401 | -------------------------------------------------------------------------------- /test/sql/redis_clean: -------------------------------------------------------------------------------- 1 | select 15 2 | 3 | flushdb 4 | 5 | -------------------------------------------------------------------------------- /test/sql/redis_fdw.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE OR REPLACE FUNCTION atsort( a text[]) 3 | RETURNS text[] 4 | LANGUAGE sql 5 | IMMUTABLE STRICT 6 | AS $function$ 7 | select array(select unnest($1) order by 1) 8 | $function$ 9 | 10 | ; 11 | 12 | 13 | 14 | create server localredis foreign data wrapper redis_fdw; 15 | 16 | create user mapping for public server localredis; 17 | 18 | -- tables for all 5 data types (4 structured plus scalar) 19 | 20 | create foreign table db15(key text, value text) 21 | server localredis 22 | options (database '15'); 23 | 24 | create foreign table db15_hash(key text, value text) 25 | server localredis 26 | options (database '15', tabletype 'hash'); 27 | 28 | create foreign table db15_set(key text, value text) 29 | server localredis 30 | options (database '15', tabletype 'set'); 31 | 32 | create foreign table db15_list(key text, value text) 33 | server localredis 34 | options (database '15', tabletype 'list'); 35 | 36 | create foreign table db15_zset(key text, value text) 37 | server localredis 38 | options (database '15', tabletype 'zset'); 39 | 40 | -- make sure they are all empty - if any are not stop the script right now 41 | 42 | \set ON_ERROR_STOP 43 | do $$ 44 | declare 45 | rows bigint; 46 | begin 47 | select into rows 48 | (select count(*) from db15) + 49 | (select count(*) from db15_hash) + 50 | (select count(*) from db15_set) + 51 | (select count(*) from db15_list) + 52 | (select count(*) from db15_zset); 53 | if rows > 0 54 | then 55 | raise EXCEPTION 'db 15 not empty'; 56 | end if; 57 | end; 58 | $$; 59 | \unset ON_ERROR_STOP 60 | 61 | 62 | -- ok, empty, so now run the setup script 63 | 64 | \! redis-cli < test/sql/redis_setup 65 | 66 | select * from db15 order by key; 67 | 68 | select * from db15 where key = 'foo'; 69 | 70 | -- hash 71 | 72 | create foreign table db15_hash_prefix(key text, value text) 73 | server localredis 74 | options (tabletype 'hash', tablekeyprefix 'hash', database '15'); 75 | 76 | create foreign table db15_hash_prefix_array(key text, value text[]) 77 | server localredis 78 | options (tabletype 'hash', tablekeyprefix 'hash', database '15'); 79 | 80 | create foreign table db15_hash_keyset_array(key text, value text[]) 81 | server localredis 82 | options (tabletype 'hash', tablekeyset 'hkeys', database '15'); 83 | 84 | select * from db15_hash_prefix order by key; 85 | select * from db15_hash_prefix where key = 'hash1'; 86 | 87 | select * from db15_hash_prefix_array order by key; 88 | select * from db15_hash_prefix_array where key = 'hash1'; 89 | 90 | select * from db15_hash_keyset_array order by key; 91 | select * from db15_hash_keyset_array where key = 'hash1'; 92 | 93 | -- a couple of nifty things we an do with hash tables 94 | 95 | select key, hstore(value) from db15_hash_prefix_array order by key; 96 | 97 | create type atab as (k1 text, k2 text, k3 text); 98 | 99 | select key, (populate_record(null::atab, hstore(value))).* 100 | from db15_hash_prefix_array 101 | order by key; 102 | 103 | -- set 104 | 105 | create foreign table db15_set_prefix(key text, value text) 106 | server localredis 107 | options (tabletype 'set', tablekeyprefix 'set', database '15'); 108 | 109 | create foreign table db15_set_prefix_array(key text, value text[]) 110 | server localredis 111 | options (tabletype 'set', tablekeyprefix 'set', database '15'); 112 | 113 | create foreign table db15_set_keyset_array(key text, value text[]) 114 | server localredis 115 | options (tabletype 'set', tablekeyset 'skeys', database '15'); 116 | 117 | -- need to use atsort() on set results to get predicable output 118 | -- since redis will give them back in arbitrary order 119 | -- means we can't show the actual value for db15_set_prefix which has it as a 120 | -- single text field 121 | 122 | select key, atsort(value::text[]) as value from db15_set_prefix order by key; 123 | select key, atsort(value::text[]) as value from db15_set_prefix where key = 'set1'; 124 | 125 | select key, atsort(value) as value from db15_set_prefix_array order by key; 126 | select key, atsort(value) as value from db15_set_prefix_array where key = 'set1'; 127 | 128 | select key, atsort(value) as value from db15_set_keyset_array order by key; 129 | select key, atsort(value) as value from db15_set_keyset_array where key = 'set1'; 130 | 131 | -- list 132 | 133 | create foreign table db15_list_prefix(key text, value text) 134 | server localredis 135 | options (tabletype 'list', tablekeyprefix 'list', database '15'); 136 | 137 | create foreign table db15_list_prefix_array(key text, value text[]) 138 | server localredis 139 | options (tabletype 'list', tablekeyprefix 'list', database '15'); 140 | 141 | create foreign table db15_list_keyset_array(key text, value text[]) 142 | server localredis 143 | options (tabletype 'list', tablekeyset 'lkeys', database '15'); 144 | 145 | select * from db15_list_prefix order by key; 146 | select * from db15_list_prefix where key = 'list1'; 147 | 148 | select * from db15_list_prefix_array order by key; 149 | select * from db15_list_prefix_array where key = 'list1'; 150 | 151 | select * from db15_list_keyset_array order by key; 152 | select * from db15_list_keyset_array where key = 'list1'; 153 | 154 | -- zset 155 | 156 | create foreign table db15_zset_prefix(key text, value text) 157 | server localredis 158 | options (tabletype 'zset', tablekeyprefix 'zset', database '15'); 159 | 160 | create foreign table db15_zset_prefix_array(key text, value text[]) 161 | server localredis 162 | options (tabletype 'zset', tablekeyprefix 'zset', database '15'); 163 | 164 | create foreign table db15_zset_keyset_array(key text, value text[]) 165 | server localredis 166 | options (tabletype 'zset', tablekeyset 'zkeys', database '15'); 167 | 168 | select * from db15_zset_prefix order by key; 169 | select * from db15_zset_prefix where key = 'zset1'; 170 | 171 | select * from db15_zset_prefix_array order by key; 172 | select * from db15_zset_prefix_array where key = 'zset1'; 173 | 174 | select * from db15_zset_keyset_array order by key; 175 | select * from db15_zset_keyset_array where key = 'zset1'; 176 | 177 | -- singleton scalar 178 | 179 | create foreign table db15_1key(value text) 180 | server localredis 181 | options (singleton_key 'foo', database '15'); 182 | 183 | select * from db15_1key; 184 | 185 | -- singleton hash 186 | 187 | create foreign table db15_1key_hash(key text, value text) 188 | server localredis 189 | options (tabletype 'hash', singleton_key 'hash1', database '15'); 190 | 191 | select * from db15_1key_hash order by key; 192 | 193 | 194 | -- singleton set 195 | 196 | create foreign table db15_1key_set(value text) 197 | server localredis 198 | options (tabletype 'set', singleton_key 'set1', database '15'); 199 | 200 | select * from db15_1key_set order by value; 201 | 202 | 203 | -- singleton list 204 | 205 | create foreign table db15_1key_list(value text) 206 | server localredis 207 | options (tabletype 'list', singleton_key 'list1', database '15'); 208 | 209 | select * from db15_1key_list order by value; 210 | 211 | 212 | -- singleton zset 213 | 214 | create foreign table db15_1key_zset(value text) 215 | server localredis 216 | options (tabletype 'zset', singleton_key 'zset1', database '15'); 217 | 218 | select * from db15_1key_zset order by value; 219 | 220 | 221 | -- singleton zset with scores 222 | 223 | create foreign table db15_1key_zset_scores(value text, score numeric) 224 | server localredis 225 | options (tabletype 'zset', singleton_key 'zset1', database '15'); 226 | 227 | select * from db15_1key_zset_scores order by score desc; 228 | 229 | 230 | -- insert delete update 231 | 232 | -- first clean the database again 233 | 234 | \! redis-cli < test/sql/redis_clean 235 | 236 | -- singleton scalar table 237 | 238 | create foreign table db15_w_1key_scalar(val text) 239 | server localredis 240 | options (singleton_key 'w_1key_scalar', database '15'); 241 | 242 | select * from db15_w_1key_scalar; 243 | 244 | insert into db15_w_1key_scalar values ('only row'); 245 | 246 | select * from db15_w_1key_scalar; 247 | 248 | insert into db15_w_1key_scalar values ('only row'); 249 | 250 | delete from db15_w_1key_scalar where val = 'not only row'; 251 | 252 | select * from db15_w_1key_scalar; 253 | 254 | update db15_w_1key_scalar set val = 'new scalar val'; 255 | 256 | select * from db15_w_1key_scalar; 257 | 258 | delete from db15_w_1key_scalar; 259 | 260 | select * from db15_w_1key_scalar; 261 | 262 | -- singleton hash 263 | 264 | create foreign table db15_w_1key_hash(key text, val text) 265 | server localredis 266 | options (singleton_key 'w_1key_hash', tabletype 'hash', database '15'); 267 | 268 | select * from db15_w_1key_hash; 269 | 270 | insert into db15_w_1key_hash values ('a','b'), ('c','d'),('e','f'); 271 | 272 | select * from db15_w_1key_hash order by key; 273 | 274 | insert into db15_w_1key_hash values ('a','b'); 275 | 276 | delete from db15_w_1key_hash where key = 'a'; 277 | 278 | delete from db15_w_1key_hash where key = 'a'; 279 | 280 | select * from db15_w_1key_hash order by key; 281 | 282 | update db15_w_1key_hash set key = 'x', val = 'y' where key = 'c'; 283 | 284 | select * from db15_w_1key_hash order by key; 285 | 286 | update db15_w_1key_hash set val = 'z' where key = 'e'; 287 | 288 | select * from db15_w_1key_hash order by key; 289 | 290 | update db15_w_1key_hash set key = 'w' where key = 'e'; 291 | 292 | select * from db15_w_1key_hash order by key; 293 | 294 | -- singleton list 295 | 296 | create foreign table db15_w_1key_list(val text) 297 | server localredis 298 | options (singleton_key 'w_1key_list', tabletype 'list', database '15'); 299 | 300 | select * from db15_w_1key_list; 301 | 302 | insert into db15_w_1key_list values ('a'), ('c'),('e'); 303 | 304 | -- for lists the order should (must) be determinate 305 | 306 | select * from db15_w_1key_list /* order by val */ ; 307 | 308 | delete from db15_w_1key_list where val = 'a'; 309 | 310 | delete from db15_w_1key_list where val = 'z'; 311 | 312 | insert into db15_w_1key_list values ('b'), ('d'),('f'),('a'); -- dups allowed here 313 | 314 | select * from db15_w_1key_list /* order by val */; 315 | 316 | update db15_w_1key_list set val = 'y'; 317 | 318 | -- singleton set 319 | 320 | create foreign table db15_w_1key_set(key text) 321 | server localredis 322 | options (singleton_key 'w_1key_set', tabletype 'set', database '15'); 323 | 324 | select * from db15_w_1key_set; 325 | 326 | insert into db15_w_1key_set values ('a'), ('c'),('e'); 327 | 328 | select * from db15_w_1key_set order by key; 329 | 330 | insert into db15_w_1key_set values ('a'); -- error - dup 331 | 332 | delete from db15_w_1key_set where key = 'c'; 333 | 334 | select * from db15_w_1key_set order by key; 335 | 336 | update db15_w_1key_set set key = 'x' where key = 'e'; 337 | 338 | select * from db15_w_1key_set order by key; 339 | 340 | -- singleton zset with scores 341 | 342 | create foreign table db15_w_1key_zset(key text, priority numeric) 343 | server localredis 344 | options (singleton_key 'w_1key_zset', tabletype 'zset', database '15'); 345 | 346 | select * from db15_w_1key_zset; 347 | 348 | insert into db15_w_1key_zset values ('a',1), ('c',5),('e',-5), ('h',10); 349 | 350 | select * from db15_w_1key_zset order by priority; 351 | 352 | insert into db15_w_1key_zset values ('a',99); 353 | 354 | delete from db15_w_1key_zset where key = 'a'; 355 | 356 | select * from db15_w_1key_zset order by priority; 357 | 358 | delete from db15_w_1key_zset where priority = '5'; 359 | 360 | select * from db15_w_1key_zset order by priority; 361 | 362 | update db15_w_1key_zset set key = 'x', priority = 99 where priority = '-5'; 363 | 364 | select * from db15_w_1key_zset order by priority; 365 | 366 | update db15_w_1key_zset set key = 'y' where key = 'h'; 367 | 368 | select * from db15_w_1key_zset order by priority; 369 | 370 | update db15_w_1key_zset set priority = 20 where key = 'y'; 371 | 372 | select * from db15_w_1key_zset order by priority; 373 | 374 | -- singleton zset no scores 375 | -- use set from last step 376 | delete from db15_w_1key_zset; 377 | 378 | insert into db15_w_1key_zset values ('e',-5); 379 | 380 | create foreign table db15_w_1key_zsetx(key text) 381 | server localredis 382 | options (singleton_key 'w_1key_zset', tabletype 'zset', database '15'); 383 | 384 | select * from db15_w_1key_zsetx; 385 | 386 | insert into db15_w_1key_zsetx values ('a'), ('c'),('e'); -- can't insert 387 | 388 | update db15_w_1key_zsetx set key = 'z' where key = 'e'; 389 | 390 | select * from db15_w_1key_zsetx order by key; 391 | 392 | delete from db15_w_1key_zsetx where key = 'z'; 393 | 394 | select * from db15_w_1key_zsetx order by key; 395 | 396 | -- non-singleton scalar table no prefix no keyset 397 | 398 | create foreign table db15_w_scalar(key text, val text) 399 | server localredis 400 | options (database '15'); 401 | 402 | select * from db15_w_scalar; 403 | 404 | insert into db15_w_scalar values ('a_ws','b'), ('c_ws','d'),('e_ws','f'); 405 | 406 | select * from db15_w_scalar order by key; 407 | 408 | delete from db15_w_scalar where key = 'a_ws'; 409 | 410 | select * from db15_w_scalar order by key; 411 | 412 | update db15_w_scalar set key = 'x_ws', val='y' where key = 'e_ws'; 413 | 414 | select * from db15_w_scalar order by key; 415 | 416 | update db15_w_scalar set key = 'z_ws' where key = 'c_ws'; 417 | 418 | select * from db15_w_scalar order by key; 419 | 420 | update db15_w_scalar set val = 'z' where key = 'z_ws'; 421 | 422 | select * from db15_w_scalar order by key; 423 | 424 | /* 425 | -- don't delete the whole namespace 426 | delete from db15_w_scalar; 427 | 428 | select * from db15_w_scalar; 429 | */ 430 | 431 | -- non-singleton scalar table keyprefix 432 | 433 | create foreign table db15_w_scalar_pfx(key text, val text) 434 | server localredis 435 | options (database '15', tablekeyprefix 'w_scalar_'); 436 | 437 | select * from db15_w_scalar_pfx; 438 | 439 | insert into db15_w_scalar_pfx values ('w_scalar_a','b'), ('w_scalar_c','d'),('w_scalar_e','f'); 440 | 441 | insert into db15_w_scalar_pfx values ('x','y'); -- prefix error 442 | 443 | insert into db15_w_scalar_pfx values ('w_scalar_a','x'); -- dup error 444 | 445 | select * from db15_w_scalar_pfx order by key; 446 | 447 | delete from db15_w_scalar_pfx where key = 'w_scalar_a'; 448 | 449 | select * from db15_w_scalar_pfx order by key; 450 | 451 | update db15_w_scalar_pfx set key = 'x', val = 'y' where key = 'w_scalar_c'; -- prefix err 452 | update db15_w_scalar_pfx set key = 'x' where key = 'w_scalar_c'; -- prefix err 453 | 454 | update db15_w_scalar_pfx set key = 'w_scalar_x', val = 'y' where key = 'w_scalar_c'; 455 | 456 | select * from db15_w_scalar_pfx order by key; 457 | 458 | update db15_w_scalar_pfx set key = 'w_scalar_z' where key = 'w_scalar_x'; 459 | 460 | select * from db15_w_scalar_pfx order by key; 461 | 462 | update db15_w_scalar_pfx set val = 'w' where key = 'w_scalar_e'; 463 | 464 | select * from db15_w_scalar_pfx order by key; 465 | 466 | delete from db15_w_scalar_pfx; 467 | 468 | select * from db15_w_scalar_pfx order by key; 469 | 470 | -- non-singleton scalar table keyset 471 | 472 | create foreign table db15_w_scalar_kset(key text, val text) 473 | server localredis 474 | options (database '15', tablekeyset 'w_scalar_kset'); 475 | 476 | select * from db15_w_scalar_kset order by key; 477 | 478 | insert into db15_w_scalar_kset values ('a_wsks','b'), ('c_wsks','d'),('e_wsks','f'); 479 | 480 | insert into db15_w_scalar_kset values ('a_wsks','x'); -- dup error 481 | 482 | select * from db15_w_scalar_kset order by key; 483 | 484 | delete from db15_w_scalar_kset where key = 'a_wsks'; 485 | 486 | select * from db15_w_scalar_kset order by key; 487 | 488 | update db15_w_scalar_kset set key = 'x_wsks', val = 'y' where key = 'c_wsks'; 489 | 490 | select * from db15_w_scalar_kset order by key; 491 | 492 | update db15_w_scalar_kset set key = 'z_wsks' where key = 'x_wsks'; 493 | 494 | select * from db15_w_scalar_kset order by key; 495 | 496 | update db15_w_scalar_kset set val = 'w' where key = 'e_wsks'; 497 | 498 | select * from db15_w_scalar_kset order by key; 499 | 500 | delete from db15_w_scalar_kset; 501 | 502 | select * from db15_w_scalar_kset order by key; 503 | 504 | 505 | -- non-singleton set table no prefix no keyset 506 | 507 | -- non-array case -- fails 508 | create foreign table db15_w_set_nonarr(key text, val text) 509 | server localredis 510 | options (database '15', tabletype 'set'); 511 | 512 | insert into db15_w_set_nonarr values ('nkseta','{b,c,d}'), ('nksetc','{d,e,f}'),('nksete','{f,g,h}'); 513 | 514 | /* 515 | -- namespace too polluted for this case 516 | create foreign table db15_w_set(key text, val text[]) 517 | server localredis 518 | options (database '15', tabletype 'set'); 519 | 520 | select * from db15_w_set; 521 | 522 | insert into db15_w_set values ('nkseta','{b,c,d}'), ('nksetc','{d,e,f}'),('nksete','{f,g,h}'); 523 | 524 | select * from db15_w_set; 525 | 526 | delete from db15_w_set where key = 'nkseta'; 527 | 528 | select * from db15_w_set; 529 | 530 | delete from db15_w_set; 531 | 532 | select * from db15_w_set; 533 | 534 | */ 535 | 536 | -- non-singleton set table keyprefix 537 | 538 | create foreign table db15_w_set_pfx(key text, val text[]) 539 | server localredis 540 | options (database '15', tabletype 'set', tablekeyprefix 'w_set_'); 541 | 542 | select * from db15_w_set_pfx; 543 | 544 | insert into db15_w_set_pfx values ('w_set_a','{b,c,d}'), ('w_set_c','{d,e,f}'),('w_set_e','{f,g,h}'); 545 | 546 | insert into db15_w_set_pfx values ('x','{y}'); -- prefix error 547 | 548 | insert into db15_w_set_pfx values ('w_set_a','{x,y,z}'); -- dup error 549 | 550 | select key, atsort(val) as val from db15_w_set_pfx order by key; 551 | 552 | delete from db15_w_set_pfx where key = 'w_set_a'; 553 | 554 | select key, atsort(val) as val from db15_w_set_pfx order by key; 555 | 556 | update db15_w_set_pfx set key = 'x' where key = 'w_set_c'; -- prefix err 557 | update db15_w_set_pfx set key = 'x', val = '{y}' where key = 'w_set_c'; -- prefix err 558 | 559 | update db15_w_set_pfx set key = 'w_set_x', val = '{x,y,z}' where key = 'w_set_c'; 560 | 561 | select key, atsort(val) as val from db15_w_set_pfx order by key; 562 | 563 | update db15_w_set_pfx set key = 'w_set_z' where key = 'w_set_x'; 564 | 565 | select key, atsort(val) as val from db15_w_set_pfx order by key; 566 | 567 | update db15_w_set_pfx set val = '{q,r,s}' where key = 'w_set_e'; 568 | 569 | select key, atsort(val) as val from db15_w_set_pfx order by key; 570 | 571 | delete from db15_w_set_pfx; 572 | 573 | select key, atsort(val) as val from db15_w_set_pfx order by key; 574 | 575 | 576 | -- non-singleton set table keyset 577 | 578 | create foreign table db15_w_set_kset(key text, val text[]) 579 | server localredis 580 | options (database '15', tabletype 'set', tablekeyset 'w_set_kset'); 581 | 582 | select * from db15_w_set_kset; 583 | 584 | insert into db15_w_set_kset values ('a_wsk','{b,c,d}'), ('c_wsk','{d,e,f}'),('e_wsk','{f,g,h}'); 585 | 586 | insert into db15_w_set_kset values ('a_wsk','{x}'); -- dup error 587 | 588 | select key, atsort(val) as val from db15_w_set_kset order by key; 589 | 590 | delete from db15_w_set_kset where key = 'a_wsk'; 591 | 592 | select key, atsort(val) as val from db15_w_set_kset order by key; 593 | 594 | update db15_w_set_kset set key = 'x_wsk', val = '{x,y,z}' where key = 'c_wsk'; 595 | 596 | select key, atsort(val) as val from db15_w_set_kset order by key; 597 | 598 | update db15_w_set_kset set key = 'z_wsk' where key = 'x_wsk'; 599 | 600 | select key, atsort(val) as val from db15_w_set_kset order by key; 601 | 602 | update db15_w_set_kset set val = '{q,r,s}' where key = 'e_wsk'; 603 | 604 | select key, atsort(val) as val from db15_w_set_kset order by key; 605 | 606 | delete from db15_w_set_kset; 607 | 608 | select * from db15_w_set_kset; 609 | 610 | 611 | -- non-singleton list table keyprefix 612 | 613 | create foreign table db15_w_list_pfx(key text, val text[]) 614 | server localredis 615 | options (database '15', tabletype 'list', tablekeyprefix 'w_list_'); 616 | 617 | select * from db15_w_list_pfx; 618 | 619 | insert into db15_w_list_pfx values ('w_list_a','{b,c,d}'), ('w_list_c','{d,e,f}'),('w_list_e','{f,g,h}'); 620 | 621 | insert into db15_w_list_pfx values ('x','{y}'); -- prefix error 622 | 623 | insert into db15_w_list_pfx values ('w_list_a','{x,y,z}'); -- dup error 624 | 625 | select * from db15_w_list_pfx order by key; 626 | 627 | delete from db15_w_list_pfx where key = 'w_list_a'; 628 | 629 | select * from db15_w_list_pfx order by key; 630 | 631 | update db15_w_list_pfx set key = 'x' where key = 'w_list_c'; -- prefix err 632 | update db15_w_list_pfx set key = 'x', val = '{y}' where key = 'w_list_c'; -- prefix err 633 | 634 | update db15_w_list_pfx set key = 'w_list_x', val = '{x,y,z}' where key = 'w_list_c'; 635 | 636 | select key, atsort(val) as val from db15_w_list_pfx order by key; 637 | 638 | update db15_w_list_pfx set key = 'w_list_z' where key = 'w_list_x'; 639 | 640 | select key, atsort(val) as val from db15_w_list_pfx order by key; 641 | 642 | update db15_w_list_pfx set val = '{q,r,s}' where key = 'w_list_e'; 643 | 644 | select key, atsort(val) as val from db15_w_list_pfx order by key; 645 | 646 | delete from db15_w_list_pfx; 647 | 648 | select * from db15_w_list_pfx; 649 | 650 | -- non-singleton list table keyset 651 | 652 | create foreign table db15_w_list_kset(key text, val text[]) 653 | server localredis 654 | options (database '15', tabletype 'list', tablekeyset 'w_list_kset'); 655 | 656 | select * from db15_w_list_kset; 657 | 658 | insert into db15_w_list_kset values ('a_wlk','{b,c,d}'), ('c_wlk','{d,e,f}'),('e_wlk','{f,g,h}'); 659 | 660 | insert into db15_w_list_kset values ('a_wlk','{x}'); -- dup error 661 | 662 | select * from db15_w_list_kset order by key; 663 | 664 | delete from db15_w_list_kset where key = 'a_wlk'; 665 | 666 | select * from db15_w_list_kset order by key; 667 | 668 | update db15_w_list_kset set key = 'x_wlk', val = '{x,y,z}' where key = 'c_wlk'; 669 | 670 | select key, atsort(val) as val from db15_w_list_kset order by key; 671 | 672 | update db15_w_list_kset set key = 'z_wlk' where key = 'x_wlk'; 673 | 674 | select key, atsort(val) as val from db15_w_list_kset order by key; 675 | 676 | update db15_w_list_kset set val = '{q,r,s}' where key = 'e_wlk'; 677 | 678 | select key, atsort(val) as val from db15_w_list_kset order by key; 679 | 680 | delete from db15_w_list_kset; 681 | 682 | select * from db15_w_list_kset; 683 | 684 | 685 | 686 | -- non-singleton zset table keyprefix 687 | 688 | create foreign table db15_w_zset_pfx(key text, val text[]) 689 | server localredis 690 | options (database '15', tabletype 'zset', tablekeyprefix 'w_zset_'); 691 | 692 | select * from db15_w_zset_pfx; 693 | 694 | insert into db15_w_zset_pfx values ('w_zset_a','{b,c,d}'), ('w_zset_c','{d,e,f}'),('w_zset_e','{f,g,h}'); 695 | 696 | insert into db15_w_zset_pfx values ('x','{y}'); -- prefix error 697 | 698 | insert into db15_w_zset_pfx values ('w_zset_a','{x,y,z}'); -- dup error 699 | 700 | select * from db15_w_zset_pfx order by key; 701 | 702 | delete from db15_w_zset_pfx where key = 'w_zset_a'; 703 | 704 | select * from db15_w_zset_pfx order by key; 705 | 706 | update db15_w_zset_pfx set key = 'x' where key = 'w_zset_c'; -- prefix err 707 | update db15_w_zset_pfx set key = 'x', val = '{y}' where key = 'w_zset_c'; -- prefix err 708 | 709 | update db15_w_zset_pfx set key = 'w_zset_x', val = '{x,y,z}' where key = 'w_zset_c'; 710 | 711 | select key, atsort(val) as val from db15_w_zset_pfx order by key; 712 | 713 | update db15_w_zset_pfx set key = 'w_zset_z' where key = 'w_zset_x'; 714 | 715 | select key, atsort(val) as val from db15_w_zset_pfx order by key; 716 | 717 | update db15_w_zset_pfx set val = '{q,r,s}' where key = 'w_zset_e'; 718 | 719 | select key, atsort(val) as val from db15_w_zset_pfx order by key; 720 | 721 | delete from db15_w_zset_pfx; 722 | 723 | select * from db15_w_zset_pfx; 724 | 725 | -- non-singleton zset table keyset 726 | 727 | create foreign table db15_w_zset_kset(key text, val text[]) 728 | server localredis 729 | options (database '15', tabletype 'zset', tablekeyset 'w_zset_kset'); 730 | 731 | select * from db15_w_zset_kset; 732 | 733 | insert into db15_w_zset_kset values ('a_wzk','{b,c,d}'), ('c_wzk','{d,e,f}'),('e_wzk','{f,g,h}'); 734 | 735 | insert into db15_w_zset_kset values ('a_wzk','{x}'); -- dup error 736 | 737 | select * from db15_w_zset_kset order by key; 738 | 739 | delete from db15_w_zset_kset where key = 'a_wzk'; 740 | 741 | select * from db15_w_zset_kset order by key; 742 | 743 | update db15_w_zset_kset set key = 'x_wlk', val = '{x,y,z}' where key = 'c_wzk'; 744 | 745 | select key, atsort(val) as val from db15_w_zset_kset order by key; 746 | 747 | update db15_w_zset_kset set key = 'z_wzk' where key = 'x_wzk'; 748 | 749 | select key, atsort(val) as val from db15_w_zset_kset order by key; 750 | 751 | update db15_w_zset_kset set val = '{q,r,s}' where key = 'e_wzk'; 752 | 753 | select key, atsort(val) as val from db15_w_zset_kset order by key; 754 | 755 | delete from db15_w_zset_kset; 756 | 757 | select * from db15_w_zset_kset; 758 | 759 | -- non-singleton hash table prefix 760 | 761 | create foreign table db15_w_hash_pfx(key text, val text[]) 762 | server localredis 763 | options (database '15', tabletype 'hash', tablekeyprefix 'w_hash_'); 764 | 765 | select * from db15_w_hash_pfx; 766 | 767 | insert into db15_w_hash_pfx values ('w_hash_e','{f,g,h}'); -- error 768 | 769 | insert into db15_w_hash_pfx values ('w_hash_e','{}'); -- error 770 | 771 | insert into db15_w_hash_pfx values ('w_hash_a','{b,c,d,e}'), ('w_hash_c','{f,g,h,i}'),('w_hash_e','{j,k}'); 772 | 773 | insert into db15_w_hash_pfx values ('x','{y,z}'); -- prefix error 774 | 775 | insert into db15_w_hash_pfx values ('w_hash_a','{y,z}'); -- dup error 776 | 777 | select * from db15_w_hash_pfx order by key; 778 | 779 | delete from db15_w_hash_pfx where key = 'w_hash_a'; 780 | 781 | select * from db15_w_hash_pfx order by key; 782 | 783 | update db15_w_hash_pfx set key = 'x' where key = 'w_hash_c'; -- prefix err 784 | update db15_w_hash_pfx set key = 'x', val = '{y,z}' where key = 'w_hash_c'; -- prefix err 785 | 786 | update db15_w_hash_pfx set key = 'w_hash_x', val = '{x,y,z}' where key = 'w_hash_c'; -- err 787 | 788 | update db15_w_hash_pfx set key = 'w_hash_x', val = '{w,x,y,z}' where key = 'w_hash_c'; 789 | 790 | select key, val from db15_w_hash_pfx order by key; 791 | 792 | update db15_w_hash_pfx set key = 'w_hash_z' where key = 'w_hash_x'; 793 | 794 | select key, val from db15_w_hash_pfx order by key; 795 | 796 | update db15_w_hash_pfx set val = '{q,r,s}' where key = 'w_hash_e'; 797 | 798 | select key, val from db15_w_hash_pfx order by key; 799 | 800 | delete from db15_w_hash_pfx; 801 | 802 | select * from db15_w_hash_pfx; 803 | 804 | --non-singleton hash table keyset 805 | 806 | create foreign table db15_w_hash_kset(key text, val text[]) 807 | server localredis 808 | options (database '15', tabletype 'hash', tablekeyset 'w_hash_kset'); 809 | 810 | select * from db15_w_hash_kset; 811 | 812 | insert into db15_w_hash_pfx values ('e_whk','{f,g,h}'); -- error 813 | 814 | insert into db15_w_hash_pfx values ('e_whk','{}'); -- error 815 | 816 | insert into db15_w_hash_kset values ('a_whk','{b,c,d,e}'), ('c_whk','{f,g,h,i}'),('e_whk','{j,k}'); 817 | 818 | insert into db15_w_hash_kset values ('a_whk','{x,y}'); -- dup error 819 | 820 | select * from db15_w_hash_kset order by key; 821 | 822 | delete from db15_w_hash_kset where key = 'a_whk'; 823 | 824 | select * from db15_w_hash_kset order by key; 825 | 826 | update db15_w_hash_kset set key = 'x_whk', val = '{w,x,y,z}' where key = 'c_whk'; 827 | 828 | select key, val from db15_w_hash_kset order by key; 829 | 830 | update db15_w_hash_kset set key = 'z_whk' where key = 'x_whk'; 831 | 832 | select key, val from db15_w_hash_kset order by key; 833 | 834 | update db15_w_hash_kset set val = '{q,r}' where key = 'e_whk'; 835 | 836 | select key, val from db15_w_hash_kset order by key; 837 | 838 | delete from db15_w_hash_kset; 839 | 840 | select * from db15_w_hash_kset; 841 | 842 | -- now clean up for the cursor tests 843 | 844 | \! redis-cli < test/sql/redis_clean 845 | 846 | -- cursor tests 847 | 848 | create foreign table db15bigprefixscalar ( 849 | key text not null, 850 | val text 851 | ) 852 | server localredis 853 | options (database '15', tablekeyprefix 'w_scalar_'); 854 | 855 | create foreign table db15bigkeysetscalar ( 856 | key text not null, 857 | val text 858 | ) 859 | server localredis 860 | options (database '15', tablekeyset 'w_kset'); 861 | 862 | insert into db15 863 | select 'junk' || x, 'junk' 864 | from generate_series(1,10000) as x; 865 | 866 | insert into db15bigprefixscalar 867 | select 'w_scalar_' || x::text, 'val ' || x::text 868 | from generate_series (1,10000) as x; 869 | 870 | insert into db15bigkeysetscalar 871 | select 'key_' || x::text, 'val ' || x::text 872 | from generate_series (1,10000) as x; 873 | 874 | insert into db15 875 | select 'junk' || x, 'junk' 876 | from generate_series(10001, 20000) as x; 877 | 878 | insert into db15bigprefixscalar 879 | select 'w_scalar_' || x::text, 'val ' || x::text 880 | from generate_series (10001, 20000) as x; 881 | 882 | insert into db15bigkeysetscalar 883 | select 'key_' || x::text, 'val ' || x::text 884 | from generate_series (10001, 20000) as x; 885 | 886 | select count(*) from db15; 887 | 888 | select count(*) from db15bigprefixscalar; 889 | 890 | select count(*) from db15bigkeysetscalar; 891 | 892 | -- all done, so now blow everything in the db away again 893 | 894 | \! redis-cli < test/sql/redis_clean 895 | 896 | -------------------------------------------------------------------------------- /test/sql/redis_setup: -------------------------------------------------------------------------------- 1 | select 15 2 | 3 | set foo bar 4 | set baz blurfl 5 | 6 | sadd set1 m1 m2 m3 m4 m5 m6 m7 m8 7 | sadd set2 m8 m9 m10 m11 m12 8 | 9 | lpush list1 e1 e2 e3 e4 e5 e6 10 | lpush list2 e7 e8 e9 e10 11 | 12 | hmset hash1 k1 v1 k2 v2 k3 v3 k4 v4 13 | hmset hash2 k1 v5 k2 v6 k3 v7 k4 v8 14 | 15 | zadd zset1 1 z1 2 z2 3 z3 4 z4 5 z5 6 z6 16 | zadd zset2 1 z7 2 z8 3 z9 4 z10 5 z11 6 z12 17 | 18 | sadd hkeys hash1 hash2 19 | sadd lkeys list1 list2 20 | sadd skeys set1 set2 21 | sadd zkeys zset1 zset2 22 | 23 | 24 | 25 | 26 | --------------------------------------------------------------------------------