prestogres 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog CHANGED
@@ -1,4 +1,15 @@
1
1
 
2
+ 2014-03-05 version 0.4.0:
3
+
4
+ * Preserve OID of tables when clients SELECT from system catalogs
5
+ * Increased maximum rewritten query length to 32768 bytes
6
+ * Fixed protocol version v2 handling
7
+ * Fixed 'too long error message' errors
8
+ * Fixed build scripts
9
+ * Add "LIMIT 1000" to "SELECT * FROM table" queries automatically
10
+ (ad-hoc fix for Tableau)
11
+
12
+
2
13
  2014-02-07 version 0.3.0:
3
14
 
4
15
  * Fixed system catalog queries to support multiple schemas
data/README.md CHANGED
@@ -14,54 +14,92 @@ You can use any PostgreSQL clients (see also *Limitation* section):
14
14
 
15
15
  Prestogres also offers password-based authentication and SSL.
16
16
 
17
+ ## Documents
18
+
19
+ * [How it works?](#how-it-works)
20
+ * [Limitation](#limitation)
21
+ * [Installation](#installation)
22
+ * [Running servers](#running-servers)
23
+ * [Mac OS X](#mac-os-x)
24
+ * [Configuration](#configuration)
25
+ * [pgpool.conf file](#pgpoolconf-file)
26
+ * [pool_hba.conf file](#pool_hbaconf-file)
27
+ * [prestogres_md5 method](#prestogres_md5-method)
28
+ * [prestogres_external method](#prestogres_external-method)
29
+ * [prestogres command](#prestogres-command)
30
+ * [Development](#development)
31
+
32
+ ---
33
+
17
34
  ## How it works?
18
35
 
19
36
  Prestogres uses modified **[pgpool-II](http://www.pgpool.net/)** to rewrite queries before sending them to PostgreSQL.
20
- pgpool-II is originally an open-source middleware to provide connection pool and other features to PostgreSQL.
37
+ pgpool-II is originally an open-source middleware to provide connection pool and other features to PostgreSQL. For regular SQL query, patched pgpool-II wraps it in **run_presto_astemp_table(..., 'SELECT ... FROM ...')** function. This function sends the query to Presto:
21
38
 
22
- ```
23
- PostgreSQL protocol Presto protocol (HTTP)
24
- / /
25
- / +-----------+ +------------+ / +--------+
26
- client ---> | pgpool-II | ---> | PostgreSQL | ---> | Presto |
27
- +-----------+ +------------+ +--------+
28
- \ \
29
- rewrite queries run custom functions
30
- | |
31
- +-----------------------------------+
32
- Prestogres
33
- ```
39
+ ![Regular SQL](https://gist.github.com/frsyuki/9012980/raw/2782f51cd3c708254ac64c4c30b7bff0e7894640/figure1.png)
40
+
41
+ If the query selects records from system catalog (e.g. `\\d` command by psql), patched pgpool-II wraps the query in `run_system_catalog_as_temp_table` function. It gets table list from Presto, and actually runs **CREATE TABLE** to create records in system catalog on PostgreSQL. Then the original query runs as usual on PostgreSQL:
42
+
43
+ ![System catalog access](https://gist.github.com/frsyuki/9012980/raw/f869bd3dd59ab22f439165972c29d122ea560694/figure2.png)
34
44
 
35
- 1. **Client** sends a query to a patched pgpool-II
36
- 2. **pgpool-II** rewrites the query to `SELECT run_presto_as_temp_table(..., '...original SELECT query...')` + `SELECT * FROM presto_result`.
37
- 3. **PostgreSQL** runs `run_presto_as_temp_table`. This custom function runs a query on Presto and writes results into a temporary table `presto_result`.
45
+ In fact there're some other tricks. See [pgsql/prestogres.py](pgsql/prestogres.py) for the real behavior.
46
+
47
+ ---
38
48
 
39
49
  ## Limitation
40
50
 
41
51
  * Extended query is not supported ([PostgreSQL Frontend/Backend Protocol](http://www.postgresql.org/docs/9.3/static/protocol.html))
42
- * ODBC driver needs to set **UseServerSidePrepare=0** (Server side prepare: no) property
43
- * ODBC driver needs to use "Unicode" mode
44
- * JDBC driver needs to set **protocolVersion=2** property
45
- * DECLARE/FETCH is not supported
52
+ * ODBC driver needs to set:
53
+ * **UseServerSidePrepare=0** (Server side prepare: no) property
54
+ * **UseDeclareFetch=0** (Use Declare/Fetch: no) property
55
+ * **Unicode** mode
56
+ * JDBC driver needs to set:
57
+ * **protocolVersion=2** property
58
+ * Cursor (DECLARE/FETCH) is not supported
59
+
60
+ ---
46
61
 
47
62
  ## Installation
48
63
 
64
+ ### 1. Install PostgreSQL >= 9.3
65
+
66
+ You need to install PostgreSQL separately. Following commands install PostgreSQL 9.3 from postgresql.org:
67
+
68
+ **Ubuntu/Debian:**
69
+
49
70
  ```
50
- $ gem install prestogres --no-ri --no-rdoc
71
+ wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
72
+ sudo apt-get update
73
+ sudo apt-get install postgresql-9.3 postgresql-server-dev-9.3 postgresql-plpython-9.3
74
+ sudo apt-get install gcc make libssl-dev libpcre3-dev
75
+ sudo apt-get install ruby ruby-dev
51
76
  ```
52
77
 
53
- Prestogres package installs patched pgpool-II but doesn't install PostgreSQL. You need to install PostgreSQL >= 9.3 (with python support) separately.
78
+ **RedHat/CentOS:**
54
79
 
55
- * If you don't have **gem** command, install Ruby >= 1.9.0 first
56
- * If installation failed, you may need to install following packages using apt or yum:
57
- * basic toolchain (gcc, make, etc.)
58
- * OpenSSL dev package (Debian/Ubuntu: **libssl-dev**, RedHat/CentOS: **openssl-dev**)
59
- * PostgreSQL server dev package (Debian/Ubuntu: **postgresql-server-dev-9.3**, RedHat/CentOS: **postgresql-devel**)
80
+ ```
81
+ sudo yum install http://yum.postgresql.org/9.3/redhat/rhel-6-x86_64/pgdg-redhat93-9.3-1.noarch.rpm
82
+ sudo yum install postgresql93-server postgresql93-contrib postgresql93-devel
83
+ sudo yum install gcc make openssl-devel pcre-devel
84
+ sudo yum install ruby ruby-devel
85
+ ```
86
+
87
+ **Mac OS X:**
60
88
 
61
- ### Installation FAQ
89
+ ```
90
+ ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
91
+ brew install postgresql
92
+ ```
93
+
94
+ ### 2. Install Prestogres
95
+
96
+ ```
97
+ $ sudo gem install prestogres --no-ri --no-rdoc
98
+ ```
62
99
 
100
+ ---
63
101
 
64
- ## Runing servers
102
+ ## Running servers
65
103
 
66
104
  You need to run 2 server programs: pgpool-II and PostgreSQL.
67
105
  You can use `prestogres` command to setup & run them as following:
@@ -86,26 +124,42 @@ $ psql -h localhost -p 9900 -U pg postgres
86
124
 
87
125
  If configuration is correct, you can run `SELECT * FROM sys.node;` query. Otherwise, see log files in **./pgdata/log/** directory.
88
126
 
89
- #### Mac OS X
127
+ #### Setting shmem max parameter
90
128
 
91
- You need to run following commands before running PostgreSQL on Mac OS X:
129
+ Probably above command fails first time! Error message is:
92
130
 
93
131
  ```
94
- $ sudo sysctl -w kern.sysv.shmmax=1073741824
95
- $ sudo sysctl -w kern.sysv.shmall=1073741824
132
+ FATAL: could not create shared memory segment: Cannot allocate memory
133
+ DETAIL: Failed system call was shmget(key=6432001, size=3809280, 03600).
134
+ HINT: This error usually means that PostgreSQL's request for a shared memory segment exceeded
135
+ available memory or swap space, or exceeded your kernel's SHMALL parameter. You can either
136
+ reduce the request size or reconfigure the kernel with larger SHMALL. To reduce the request
137
+ size (currently 3809280 bytes), reduce PostgreSQL's shared memory usage, perhaps by reducing
138
+ shared_buffers or max_connections.
96
139
  ```
97
140
 
98
- if you got following log messages:
141
+ You need to set 2 kernel parameters to run PostgreSQL.
142
+
143
+ **Linux:**
99
144
 
100
145
  ```
101
- FATAL: could not create shared memory segment: Cannot allocate memory
102
- DETAIL: Failed system call was shmget(key=6432001, size=3809280, 03600).
103
- HINT: This error usually means that PostgreSQL's request for a shared memory segment exceeded available memory or swap space, or exceeded your kernel's SHMALL parameter. You can either reduce the request size or reconfigure the kernel with larger SHMALL. To reduce the request size (currently 3809280 bytes), reduce PostgreSQL's shared memory usage, perhaps by reducing shared_buffers or max_connections.
146
+ sudo sudo -c "echo kernel.shmmax = 17179869184 >> /etc/sysctl.conf"
147
+ sudo sudo -c "echo kernel.shmall = 4194304 >> /etc/sysctl.conf"
148
+ sudo sysctl -p /etc/sysctl.conf
104
149
  ```
105
150
 
151
+ **Mac OS X:**
152
+
153
+ ```
154
+ $ sudo sysctl -w kern.sysv.shmmax=1073741824
155
+ $ sudo sysctl -w kern.sysv.shmall=1073741824
156
+ ```
157
+
158
+ ---
159
+
106
160
  ## Configuration
107
161
 
108
- ### pgool.conf file
162
+ ### pgpool.conf file
109
163
 
110
164
  Please read [pgpool-II documentation](http://www.pgpool.net/docs/latest/pgpool-en.html) for most of parameters.
111
165
  Following parameters are unique to Prestogres:
@@ -184,7 +238,6 @@ See *pgool.conf file* section for available parameters.
184
238
 
185
239
  If you want to reject this connection, the program exists with non-0 status code.
186
240
 
187
-
188
241
  ### Creating database on PostgreSQL
189
242
 
190
243
  Prestogres setups a database named *postgres* on PostgreSQL by default. But you may want to create other databases
@@ -221,6 +274,8 @@ commands:
221
274
  show_init_sql display statements to initialize a new PostgreSQL database
222
275
  ```
223
276
 
277
+ ---
278
+
224
279
  ## Development
225
280
 
226
281
  To install git HEAD, use following commands to build:
@@ -242,3 +297,9 @@ $ bundle exec rake
242
297
  $ gem install --no-ri --no-rdoc pkg/prestogres-*.gem
243
298
  # if this command failed, you may need to install toolchain (gcc, etc.) to build pgpool-II
244
299
  ```
300
+
301
+ ___
302
+
303
+ Prestogres is licensed under Apache License, Version 2.0.
304
+ Copyright (C) 2014 Sadayuki Furuhashi
305
+
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.0
1
+ 0.4.0
@@ -68,11 +68,11 @@ usage nil unless config[:command]
68
68
  args = ARGV[arg_offset..-1]
69
69
 
70
70
  setup_params = {
71
- port: 9900,
72
- backend_port: 6432,
73
- pgpool_pid_file: nil,
74
- pgpool_status_dir: nil,
75
- unix_socket_directory: nil,
71
+ port: 9900,
72
+ backend_port: 6432,
73
+ pgpool_pid_file: nil,
74
+ pgpool_status_dir: nil,
75
+ unix_socket_directory: nil,
76
76
  }
77
77
 
78
78
  ENV['PATH'] = "#{ENV['PATH']}:#{config[:bin_path]}"
@@ -269,7 +269,7 @@ white_function_list = ''
269
269
  # Comma separated list of function names
270
270
  # that don't write to database
271
271
  # Regexp are accepted
272
- black_function_list = 'currval,lastval,nextval,setval,current_setting'
272
+ black_function_list = 'currval,lastval,nextval,setval,current_setting,version,pg_client_encoding'
273
273
  # Comma separated list of function names
274
274
  # that write to database
275
275
  # Regexp are accepted
data/ext/depend CHANGED
@@ -6,29 +6,28 @@ narchdir = $(if $(rubyarchdir),$(rubyarchdir),$(RUBYARCHDIR))
6
6
  # hack for RubyGems >= 1.8.26
7
7
  final_archdir = $(if $(findstring .gem.,$(narchdir)),$(dir $(narchdir)),$(narchdir))
8
8
 
9
- INSTALL_DATA = $(realpath $(final_archdir))/prestogres
9
+ NATIVE_PREFIX = $(abspath $(final_archdir)/prestogres)
10
10
 
11
- PGPOOL2_PATH = $(realpath $(srcdir)/../pgpool2)
11
+ CFLAGS += -DPRESTOGRES_PREFIX="\"$(NATIVE_PREFIX)\""
12
12
 
13
- CFLAGS += -DPRESTOGRES_PREFIX="\"$(INSTALL_DATA)\""
13
+ all: native
14
+ clean: clean-native
15
+ install: install-native
14
16
 
15
- all: pgpool2
17
+ PGPOOL2_PATH = $(abspath $(srcdir)/../pgpool2)
16
18
 
17
- clean: clean-pgpool2
18
-
19
- install: install-pgpool2
20
-
21
- pgpool2:
19
+ native:
22
20
  mkdir -p build
23
21
  cd build && "$(PGPOOL2_PATH)/configure" \
24
22
  CFLAGS="-I$(PGPOOL2_PATH)" \
25
23
  --with-openssl \
26
- --prefix="$(INSTALL_DATA)"
24
+ --prefix="$(NATIVE_PREFIX)"
27
25
  cd build && make sysconfdir=/opt/prestogres/etc
28
26
 
29
- clean-pgpool2:
27
+ clean-native:
30
28
  @rm -rf build
31
29
 
32
- install-pgpool2:
30
+ install-native:
33
31
  cd build && make install
34
32
 
33
+ .PHONY: native clean-native install-native
@@ -99,7 +99,7 @@ static void rebuild_startup_packet(StartupPacket *sp)
99
99
  {
100
100
  if (sp->major == PROTO_MAJOR_V2) {
101
101
  StartupPacket_v2 *sp2 = (StartupPacket_v2 *)(sp->startup_packet);
102
- strncpy(sp2->database, sp2->database, SM_DATABASE);
102
+ strncpy(sp2->database, sp->database, SM_DATABASE);
103
103
  strncpy(sp2->user, sp->user, SM_USER);
104
104
 
105
105
  } else if (sp->major == PROTO_MAJOR_V3) {
@@ -79,6 +79,9 @@
79
79
  /* Define to 1 if you have the `pam' library (-lpam). */
80
80
  #undef HAVE_LIBPAM
81
81
 
82
+ /* Define to 1 if you have the `pcre' library (-lpcre). */
83
+ #undef HAVE_LIBPCRE
84
+
82
85
  /* Define to 1 if you have the `pq' library (-lpq). */
83
86
  #undef HAVE_LIBPQ
84
87
 
@@ -11450,6 +11450,45 @@ _ACEOF
11450
11450
 
11451
11451
  fi
11452
11452
 
11453
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -lpcre" >&5
11454
+ $as_echo_n "checking for main in -lpcre... " >&6; }
11455
+ if test "${ac_cv_lib_pcre_main+set}" = set; then :
11456
+ $as_echo_n "(cached) " >&6
11457
+ else
11458
+ ac_check_lib_save_LIBS=$LIBS
11459
+ LIBS="-lpcre $LIBS"
11460
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
11461
+ /* end confdefs.h. */
11462
+
11463
+
11464
+ int
11465
+ main ()
11466
+ {
11467
+ return main ();
11468
+ ;
11469
+ return 0;
11470
+ }
11471
+ _ACEOF
11472
+ if ac_fn_c_try_link "$LINENO"; then :
11473
+ ac_cv_lib_pcre_main=yes
11474
+ else
11475
+ ac_cv_lib_pcre_main=no
11476
+ fi
11477
+ rm -f core conftest.err conftest.$ac_objext \
11478
+ conftest$ac_exeext conftest.$ac_ext
11479
+ LIBS=$ac_check_lib_save_LIBS
11480
+ fi
11481
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pcre_main" >&5
11482
+ $as_echo "$ac_cv_lib_pcre_main" >&6; }
11483
+ if test "x$ac_cv_lib_pcre_main" = x""yes; then :
11484
+ cat >>confdefs.h <<_ACEOF
11485
+ #define HAVE_LIBPCRE 1
11486
+ _ACEOF
11487
+
11488
+ LIBS="-lpcre $LIBS"
11489
+
11490
+ fi
11491
+
11453
11492
 
11454
11493
  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5
11455
11494
  $as_echo_n "checking for ANSI C header files... " >&6; }
@@ -63,6 +63,7 @@ AC_CHECK_LIB(gen, main)
63
63
  AC_CHECK_LIB(PW, main)
64
64
  AC_CHECK_LIB(resolv, main)
65
65
  AC_CHECK_LIB(crypt, main)
66
+ AC_CHECK_LIB(pcre, main)
66
67
 
67
68
  dnl Checks for header files.
68
69
  AC_HEADER_STDC
@@ -32,6 +32,8 @@
32
32
  #include <netinet/in.h>
33
33
  #include <stdlib.h>
34
34
 
35
+ #include <pcre.h>
36
+
35
37
  /*
36
38
  * Where to send query
37
39
  */
@@ -293,7 +295,11 @@ int pool_virtual_master_db_node_id(void)
293
295
  #define PRESTO_RESULT_TABLE_NAME "presto_result"
294
296
  #endif
295
297
 
296
- char rewrite_query_string_buffer[QUERY_STRING_BUFFER_LEN];
298
+ #ifndef PRESTO_REWRITE_QUERY_SIZE_LIMIT
299
+ #define PRESTO_REWRITE_QUERY_SIZE_LIMIT 32768
300
+ #endif
301
+
302
+ char rewrite_query_string_buffer[PRESTO_REWRITE_QUERY_SIZE_LIMIT];
297
303
 
298
304
  static char *strcpy_capped(char *buffer, int length, const char *string)
299
305
  {
@@ -362,7 +368,7 @@ static void do_replace_query(POOL_QUERY_CONTEXT* query_context, const char *quer
362
368
  query_context->original_length = strlen(dupq) + 1;
363
369
  }
364
370
 
365
- static void rewrite_error_query(POOL_QUERY_CONTEXT* query_context, const char *message, const char* errcode)
371
+ static void rewrite_error_query_static(POOL_QUERY_CONTEXT* query_context, const char *message, const char* errcode)
366
372
  {
367
373
  char *buffer, *bufend;
368
374
 
@@ -381,14 +387,39 @@ static void rewrite_error_query(POOL_QUERY_CONTEXT* query_context, const char *m
381
387
  buffer = strcpy_capped(buffer, bufend - buffer, "; end $$ language plpgsql");
382
388
 
383
389
  if (buffer == NULL) {
384
- do_replace_query(query_context,
385
- "do $$ begin raise ecxeption 'too long error message'; end $$ language plpgsql");
386
- return;
390
+ buffer = rewrite_query_string_buffer;
391
+ bufend = buffer + sizeof(rewrite_query_string_buffer);
392
+
393
+ buffer = strcpy_capped(buffer, bufend - buffer, "do $$ begin raise ecxeption 'too long error message'");
394
+ buffer = strcpy_capped(buffer, bufend - buffer, " using errcode = E'");
395
+ buffer = strcpy_capped_escaped(buffer, bufend - buffer, errcode, "'\\$");
396
+ buffer = strcpy_capped(buffer, bufend - buffer, "'; end $$ language plpgsql");
397
+
398
+ if (buffer == NULL) {
399
+ do_replace_query(query_context,
400
+ "do $$ begin raise ecxeption 'too long error message'; end $$ language plpgsql");
401
+ return;
402
+ }
387
403
  }
388
404
 
389
405
  do_replace_query(query_context, rewrite_query_string_buffer);
390
406
  }
391
407
 
408
+ static void rewrite_error_query(POOL_QUERY_CONTEXT* query_context, char *message, const char* errcode)
409
+ {
410
+ /* 20 is for escape characters */
411
+ const size_t static_length = strlen("do $$ begin raise exception '%', E'' using errcode = E'XXXXX'; end $$ language plpgsql") + 20;
412
+
413
+ if (sizeof(rewrite_query_string_buffer) < strlen(message) + static_length) {
414
+ message[sizeof(rewrite_query_string_buffer) - static_length - 3] = '.';
415
+ message[sizeof(rewrite_query_string_buffer) - static_length - 2] = '.';
416
+ message[sizeof(rewrite_query_string_buffer) - static_length - 1] = '.';
417
+ message[sizeof(rewrite_query_string_buffer) - static_length - 0] = '\0';
418
+ }
419
+
420
+ rewrite_error_query_static(query_context, message, errcode);
421
+ }
422
+
392
423
  static void run_clear_query(POOL_SESSION_CONTEXT* session_context, POOL_QUERY_CONTEXT* query_context)
393
424
  {
394
425
  // TODO
@@ -413,7 +444,7 @@ static void run_and_rewrite_system_catalog_query(POOL_SESSION_CONTEXT* session_c
413
444
  buffer = strcpy_capped(buffer, bufend - buffer, "'");
414
445
 
415
446
  if (buffer == NULL) {
416
- rewrite_error_query(query_context, "schema name too long", NULL);
447
+ rewrite_error_query_static(query_context, "schema name too long", NULL);
417
448
  return;
418
449
  }
419
450
 
@@ -425,7 +456,7 @@ static void run_and_rewrite_system_catalog_query(POOL_SESSION_CONTEXT* session_c
425
456
  if (errmsg != NULL) {
426
457
  rewrite_error_query(query_context, errmsg, errcode);
427
458
  } else if (status != POOL_CONTINUE) {
428
- rewrite_error_query(query_context, "Unknown execution error", NULL);
459
+ rewrite_error_query_static(query_context, "Unknown execution error", NULL);
429
460
  }
430
461
 
431
462
  /* build run_system_catalog_as_temp_table query */
@@ -447,7 +478,7 @@ static void run_and_rewrite_system_catalog_query(POOL_SESSION_CONTEXT* session_c
447
478
  buffer = strcpy_capped(buffer, bufend - buffer, "')");
448
479
 
449
480
  if (buffer == NULL) {
450
- rewrite_error_query(query_context, "metadata too long", NULL);
481
+ rewrite_error_query_static(query_context, "metadata too long", NULL);
451
482
  return;
452
483
  }
453
484
 
@@ -459,7 +490,7 @@ static void run_and_rewrite_system_catalog_query(POOL_SESSION_CONTEXT* session_c
459
490
  if (errmsg != NULL) {
460
491
  rewrite_error_query(query_context, errmsg, errcode);
461
492
  } else if (status != POOL_CONTINUE) {
462
- rewrite_error_query(query_context, "Unknown execution error", NULL);
493
+ rewrite_error_query_static(query_context, "Unknown execution error", NULL);
463
494
  }
464
495
 
465
496
  /* rewrite query */
@@ -470,13 +501,47 @@ static void run_and_rewrite_system_catalog_query(POOL_SESSION_CONTEXT* session_c
470
501
  buffer = strcpy_capped(buffer, bufend - buffer, PRESTO_RESULT_TABLE_NAME);
471
502
 
472
503
  if (buffer == NULL) {
473
- rewrite_error_query(query_context, "query too long", NULL);
504
+ rewrite_error_query_static(query_context, "query too long", NULL);
474
505
  return;
475
506
  }
476
507
 
477
508
  do_replace_query(query_context, rewrite_query_string_buffer);
478
509
  }
479
510
 
511
+ /*
512
+ * /\A\s*select\s*\*\s*from\s+(("[^\\"]*([\\"][^\\"]*)*")|[a-zA-Z_][a-zA-Z0-9_]*)(\.(("[^\\"]*([\\"][^\\"]*)*")|[a-zA-Z_][a-zA-Z0-9_]*))?\s*(;|\z)/.to_s
513
+ */
514
+ #define AUTO_LIMIT_QUERY_PATTERN "\\A\\s*select\\s*\\*\\s*from\\s+((\"[^\\\\\"]*([\\\\\"][^\\\\\"]*)*\")|[a-zA-Z_][a-zA-Z0-9_]*)(\\.((\"[^\\\\\"]*([\\\\\"][^\\\\\"]*)*\")|[a-zA-Z_][a-zA-Z0-9_]*))?\\s*(;|\\z)"
515
+
516
+ static bool match_auto_limit_pattern(const char* query)
517
+ {
518
+ const char* errptr;
519
+ int erroffset;
520
+ pcre* pattern;
521
+ int ret;
522
+ int ovec[10];
523
+
524
+ pattern = pcre_compile(AUTO_LIMIT_QUERY_PATTERN,
525
+ PCRE_CASELESS | PCRE_NO_AUTO_CAPTURE | PCRE_UTF8, &errptr, &erroffset, NULL);
526
+ if (pattern == NULL) {
527
+ // TODO pcre pattern should be precompiled. see also pcre_study.
528
+ pool_error("match_auto_limit_pattern: invalid regexp %s at %d", errptr, erroffset);
529
+ pcre_free(pattern);
530
+ return false;
531
+ }
532
+
533
+ ret = pcre_exec(pattern, NULL, query, strlen(query), 0, 0, &ovec, sizeof(ovec));
534
+ if (ret < 0) {
535
+ // error. pattern didn't match in most of cases
536
+ pcre_free(pattern);
537
+ return false;
538
+ }
539
+
540
+ pcre_free(pattern);
541
+
542
+ return true;
543
+ }
544
+
480
545
  static void run_and_rewrite_presto_query(POOL_SESSION_CONTEXT* session_context, POOL_QUERY_CONTEXT* query_context)
481
546
  {
482
547
  char *buffer, *bufend;
@@ -519,11 +584,17 @@ static void run_and_rewrite_presto_query(POOL_SESSION_CONTEXT* session_context,
519
584
  if (c != NULL) {
520
585
  *c = save;
521
586
  }
587
+
588
+ if (match_auto_limit_pattern(query)) {
589
+ // TODO send warning message to client
590
+ pool_debug("run_and_rewrite_presto_query: adding 'limit 1000' to a SELECT * query");
591
+ buffer = strcpy_capped(buffer, bufend - buffer, " limit 1000");
592
+ }
522
593
  }
523
594
  buffer = strcpy_capped(buffer, bufend - buffer, "')");
524
595
 
525
596
  if (buffer == NULL) {
526
- rewrite_error_query(query_context, "query too long", NULL);
597
+ rewrite_error_query_static(query_context, "query too long", NULL);
527
598
  return;
528
599
  }
529
600
 
@@ -536,7 +607,7 @@ static void run_and_rewrite_presto_query(POOL_SESSION_CONTEXT* session_context,
536
607
  rewrite_error_query(query_context, errmsg, errcode);
537
608
  return;
538
609
  } else if (status != POOL_CONTINUE) {
539
- rewrite_error_query(query_context, "Unknown execution error", NULL);
610
+ rewrite_error_query_static(query_context, "Unknown execution error", NULL);
540
611
  return;
541
612
  }
542
613
 
@@ -548,7 +619,7 @@ static void run_and_rewrite_presto_query(POOL_SESSION_CONTEXT* session_context,
548
619
  buffer = strcpy_capped(buffer, bufend - buffer, PRESTO_RESULT_TABLE_NAME);
549
620
 
550
621
  if (buffer == NULL) {
551
- rewrite_error_query(query_context, "query too long", NULL);
622
+ rewrite_error_query_static(query_context, "query too long", NULL);
552
623
  return;
553
624
  }
554
625
 
@@ -564,7 +635,7 @@ void pool_where_to_send(POOL_QUERY_CONTEXT *query_context, char *query, Node *no
564
635
  POOL_CONNECTION_POOL *backend;
565
636
  int i;
566
637
 
567
- const char* rewrite_error_message;
638
+ const char* static_error_message;
568
639
  enum {
569
640
  REWRITE_CLEAR,
570
641
  REWRITE_SYSTEM_CATALOG,
@@ -676,7 +747,7 @@ void pool_where_to_send(POOL_QUERY_CONTEXT *query_context, char *query, Node *no
676
747
  */
677
748
  if (TSTATE(backend, PRIMARY_NODE_ID) == 'I' ||
678
749
  (!pool_is_writing_transaction() &&
679
- !pool_is_failed_transaction() &&
750
+ !(MAJOR(backend) == PROTO_MAJOR_V3 && pool_is_failed_transaction()) &&
680
751
  pool_get_transaction_isolation() != POOL_SERIALIZABLE))
681
752
  {
682
753
  BackendInfo *bkinfo = pool_get_node_info(session_context->load_balance_node_id);
@@ -695,7 +766,7 @@ void pool_where_to_send(POOL_QUERY_CONTEXT *query_context, char *query, Node *no
695
766
  pool_debug("prestogres: send_to_where: replication delay");
696
767
  pool_set_node_to_be_sent(query_context, PRIMARY_NODE_ID);
697
768
  rewrite_mode = REWRITE_ERROR;
698
- rewrite_error_message = "unexpected replication delay";
769
+ static_error_message = "unexpected replication delay";
699
770
  }
700
771
 
701
772
  /*
@@ -776,16 +847,27 @@ void pool_where_to_send(POOL_QUERY_CONTEXT *query_context, char *query, Node *no
776
847
  pool_debug("prestogres: send_to_where: invalid session state");
777
848
  pool_set_node_to_be_sent(query_context, PRIMARY_NODE_ID);
778
849
  rewrite_mode = REWRITE_ERROR;
779
- rewrite_error_message = "invalid session state";
850
+ static_error_message = "invalid session state";
780
851
  }
781
852
  }
853
+ else if (query_context->is_parse_error)
854
+ {
855
+ /*
856
+ * If failed to parse the query, run it on Presto because
857
+ * it may include Presto's SQL syntax extensions.
858
+ */
859
+ pool_debug("prestogres: send_to_where: parse-error");
860
+ pool_set_node_to_be_sent(query_context,
861
+ session_context->load_balance_node_id);
862
+ rewrite_mode = REWRITE_PRESTO;
863
+ }
782
864
  else
783
865
  {
784
866
  /* Send to the primary only */
785
867
  pool_debug("prestogres: send_to_where: non-select");
786
868
  pool_set_node_to_be_sent(query_context, PRIMARY_NODE_ID);
787
869
  rewrite_mode = REWRITE_ERROR;
788
- rewrite_error_message = "only SELECT is supported";
870
+ static_error_message = "only SELECT is supported";
789
871
  }
790
872
  }
791
873
  }
@@ -891,7 +973,7 @@ void pool_where_to_send(POOL_QUERY_CONTEXT *query_context, char *query, Node *no
891
973
  break;
892
974
 
893
975
  case REWRITE_ERROR:
894
- rewrite_error_query(query_context, rewrite_error_message, NULL);
976
+ rewrite_error_query_static(query_context, static_error_message, NULL);
895
977
  break;
896
978
  }
897
979
  }
@@ -48,25 +48,22 @@ def _build_create_temp_table_sql(table_name, column_names, column_types):
48
48
  return create_sql
49
49
 
50
50
  # build CREATE TABLE statement
51
- def _build_create_table_sql(schema_name, table_name, column_names, column_types, not_nulls):
52
- create_sql = "create table %s.%s (\n " % (plpy.quote_ident(schema_name), plpy.quote_ident(table_name))
51
+ def _build_alter_table_holder_sql(schema_name, table_name, column_names, column_types, not_nulls):
52
+ alter_sql = "alter table %s.%s \n " % (plpy.quote_ident(schema_name), plpy.quote_ident(table_name))
53
53
 
54
54
  first = True
55
55
  for column_name, column_type, not_null in zip(column_names, column_types, not_nulls):
56
56
  if first:
57
57
  first = False
58
58
  else:
59
- create_sql += ",\n "
59
+ alter_sql += ",\n "
60
60
 
61
- create_sql += plpy.quote_ident(column_name)
62
- create_sql += " "
63
- create_sql += column_type
61
+ alter_sql += "add %s %s" % (plpy.quote_ident(column_name), column_type)
64
62
 
65
63
  if not_null:
66
- create_sql += " not null"
64
+ alter_sql += " not null"
67
65
 
68
- create_sql += "\n)"
69
- return create_sql
66
+ return alter_sql
70
67
 
71
68
  # build INSERT INTO statement and string format to build VALUES (..), ...
72
69
  def _build_insert_into_sql(table_name, column_names):
@@ -231,6 +228,8 @@ def run_system_catalog_as_temp_table(server, user, catalog, schema, result_table
231
228
  statements = []
232
229
  schema_names = []
233
230
 
231
+ table_holder_id = 0
232
+
234
233
  for schema_name, tables in schemas.items():
235
234
  if schema_name == "sys" or schema_name == "information_schema":
236
235
  # skip system schemas
@@ -248,8 +247,17 @@ def run_system_catalog_as_temp_table(server, user, catalog, schema, result_table
248
247
  column_types.append(_pg_table_type(column.type))
249
248
  not_nulls.append(not column.nullable)
250
249
 
251
- create_sql = _build_create_table_sql(schema_name, table_name, column_names, column_types, not_nulls)
252
- statements.append(create_sql)
250
+ # rename table holder into the schema
251
+ statements.append("alter table prestogres_catalog.table_holder_%d set schema %s" % \
252
+ (table_holder_id, plpy.quote_ident(schema_name)))
253
+ statements.append("alter table %s.table_holder_%d rename to %s" % \
254
+ (plpy.quote_ident(schema_name), table_holder_id, plpy.quote_ident(table_name)))
255
+
256
+ # change columns
257
+ alter_sql = _build_alter_table_holder_sql(schema_name, table_name, column_names, column_types, not_nulls)
258
+ statements.append(alter_sql)
259
+
260
+ table_holder_id += 1
253
261
 
254
262
  # cache expires after 60 seconds
255
263
  SchemaCacheEntry.set_cache(server, user, catalog, schema, schema_names, statements, time.time() + 60)
@@ -267,45 +275,50 @@ def run_system_catalog_as_temp_table(server, user, catalog, schema, result_table
267
275
  subxact = plpy.subtransaction()
268
276
  subxact.enter()
269
277
  try:
270
- # delete all schemas excepting prestogres_catalog
278
+ # drop all schemas excepting prestogres_catalog, pg_catalog, information_schema, public
279
+ # and schema holders
271
280
  sql = "select n.nspname as schema_name from pg_catalog.pg_namespace n" \
272
281
  " where n.nspname not in ('prestogres_catalog', 'pg_catalog', 'information_schema', 'public')" \
282
+ " and n.nspname not like 'prestogres_catalog_schema_holder_%'" \
273
283
  " and n.nspname !~ '^pg_toast'"
274
284
  for row in plpy.cursor(sql):
275
285
  plpy.execute("drop schema %s cascade" % plpy.quote_ident(row["schema_name"]))
276
286
 
277
- # delete all tables in prestogres_catalog
278
- # relkind = 'r' takes only tables and skip views, indexes, etc.
279
- sql = "select n.nspname as schema_name, c.relname as table_name from pg_catalog.pg_class c" \
280
- " left join pg_catalog.pg_namespace n on n.oid = c.relnamespace" \
281
- " where c.relkind in ('r')" \
282
- " and n.nspname in ('prestogres_catalog')" \
283
- " and n.nspname !~ '^pg_toast'"
284
- for row in plpy.cursor(sql):
285
- plpy.execute("drop table %s.%s" % (plpy.quote_ident(row["schema_name"]), plpy.quote_ident(row["table_name"])))
286
-
287
- # create schemas
287
+ # alter schema holders
288
+ schema_holder_id = 0
288
289
  for schema_name in schema_names:
289
290
  try:
290
- plpy.execute("create schema %s" % plpy.quote_ident(schema_name))
291
+ plpy.execute("alter schema prestogres_catalog_schema_holder_%s rename to %s" % \
292
+ (schema_holder_id, plpy.quote_ident(schema_name)))
293
+ schema_holder_id += 1
291
294
  except:
292
- # ignore error
295
+ # ignore error?
293
296
  pass
294
297
 
295
- # create tables
298
+ # alter table holders in prestogres_catalog schema
296
299
  for statement in statements:
297
300
  plpy.execute(statement)
298
301
 
299
- # run the actual query
302
+ # drop prestogres_catalog schema
303
+ plpy.execute("drop schema prestogres_catalog cascade")
304
+
305
+ # drop schema holders
306
+ sql = "select n.nspname as schema_name from pg_catalog.pg_namespace n" \
307
+ " where n.nspname like 'prestogres_catalog_schema_holder_%'"
308
+ for row in plpy.cursor(sql):
309
+ plpy.execute("drop schema %s" % plpy.quote_ident(row["schema_name"]))
310
+
311
+ # run the actual query and save result
300
312
  metadata = plpy.execute(query)
301
313
  column_names = metadata.colnames()
302
314
  column_type_oids = metadata.coltypes()
303
315
  result = map(lambda row: map(row.get, column_names), metadata)
304
316
 
305
- # table schema
317
+ # save result schema
306
318
  oid_to_type_name = _load_oid_to_type_name_mapping(column_type_oids)
307
319
  column_types = map(oid_to_type_name.get, column_type_oids)
308
320
 
321
+ # store query cache
309
322
  query_cache[query] = QueryResult(column_names, column_types, result)
310
323
 
311
324
  finally:
@@ -325,3 +338,12 @@ def run_system_catalog_as_temp_table(server, user, catalog, schema, result_table
325
338
  e.__class__.__module__ = "__main__"
326
339
  raise
327
340
 
341
+ def create_schema_holders(count):
342
+ for i in range(count):
343
+ plpy.execute("drop schema if exists prestogres_catalog_schema_holder_%d cascade" % i)
344
+ plpy.execute("create schema prestogres_catalog_schema_holder_%d" % i)
345
+
346
+ def create_table_holders(count):
347
+ for i in range(count):
348
+ plpy.execute("create table prestogres_catalog.table_holder_%d ()" % i)
349
+
@@ -1,6 +1,8 @@
1
1
 
2
2
  create language "plpythonu";
3
3
 
4
+ drop schema if exists prestogres_catalog cascade;
5
+
4
6
  create schema prestogres_catalog;
5
7
 
6
8
  create or replace function prestogres_catalog.run_presto_as_temp_table(
@@ -27,3 +29,19 @@ import prestogres
27
29
  prestogres.run_system_catalog_as_temp_table(server, user, catalog, schema, table_name, query)
28
30
  $$ language plpythonu;
29
31
 
32
+ create or replace function prestogres_catalog.create_schema_holders("count" int)
33
+ returns void as $$
34
+ import prestogres
35
+ prestogres.create_schema_holders(count);
36
+ $$ language plpythonu;
37
+
38
+ create or replace function prestogres_catalog.create_table_holders("count" int)
39
+ returns void as $$
40
+ import prestogres
41
+ prestogres.create_table_holders(count);
42
+ $$ language plpythonu;
43
+
44
+ select prestogres_catalog.create_schema_holders(512);
45
+
46
+ select prestogres_catalog.create_table_holders(2048);
47
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prestogres
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-02-07 00:00:00.000000000 Z
12
+ date: 2014-03-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -479,18 +479,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
479
479
  - - ! '>='
480
480
  - !ruby/object:Gem::Version
481
481
  version: '0'
482
- segments:
483
- - 0
484
- hash: -1048444669451031740
485
482
  required_rubygems_version: !ruby/object:Gem::Requirement
486
483
  none: false
487
484
  requirements:
488
485
  - - ! '>='
489
486
  - !ruby/object:Gem::Version
490
487
  version: '0'
491
- segments:
492
- - 0
493
- hash: -1048444669451031740
494
488
  requirements: []
495
489
  rubyforge_project:
496
490
  rubygems_version: 1.8.23
@@ -498,3 +492,4 @@ signing_key:
498
492
  specification_version: 3
499
493
  summary: Presto PostgreSQL protocol gateway
500
494
  test_files: []
495
+ has_rdoc: false