prestogres 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -0
- data/ChangeLog +15 -0
- data/README.md +29 -30
- data/Rakefile +3 -2
- data/VERSION +1 -1
- data/bin/prestogres +69 -27
- data/config/postgresql.conf +1 -1
- data/ext/depend +13 -5
- data/pgpool2/child.c +3 -3
- data/pgpool2/pool.h +1 -1
- data/pgpool2/pool_config.c +0 -19
- data/pgpool2/pool_hba.c +7 -7
- data/pgpool2/pool_query_context.c +28 -2
- data/pgsql/prestogres.py +95 -58
- data/pgsql/setup.sql +2 -1
- metadata +28 -15
- checksums.yaml +0 -7
- data/Gemfile.lock +0 -20
data/.gitignore
CHANGED
data/ChangeLog
CHANGED
@@ -1,4 +1,18 @@
|
|
1
1
|
|
2
|
+
2014-02-07 version 0.3.0:
|
3
|
+
|
4
|
+
* Fixed system catalog queries to support multiple schemas
|
5
|
+
* Removed presto_schema parameter from pgpool.conf
|
6
|
+
* Use connecting database name as the default schema name
|
7
|
+
* Updated type mapping to map varchar type of Presto to varchar(100)
|
8
|
+
* Implemented query cache for system catalog queries
|
9
|
+
* Improved prestogres-setup command to not listen on TCP socket
|
10
|
+
* Improved prestogres-setup command to support -P option
|
11
|
+
* Fixed build scripts to work with bundler
|
12
|
+
* Fixed build scripts to work with RubyGems >= 1.8.26
|
13
|
+
* Fixed "rake install" task
|
14
|
+
|
15
|
+
|
2
16
|
2014-01-24 version 0.2.0:
|
3
17
|
|
4
18
|
* Authentication mechanisms support pg_database and pg_user options to
|
@@ -10,6 +24,7 @@
|
|
10
24
|
* Queries to system catalogs delete created tables by rollbacking
|
11
25
|
subtransaction so that other sessions does not conflict
|
12
26
|
|
27
|
+
|
13
28
|
2014-01-15 version 0.1.0:
|
14
29
|
|
15
30
|
* First release
|
data/README.md
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# 
|
2
2
|
## PostgreSQL protocol gateway for Presto
|
3
3
|
|
4
|
-
**Prestogres** is a gateway server that allows clients to use PostgreSQL protocol to run Presto
|
4
|
+
**Prestogres** is a gateway server that allows clients to use PostgreSQL protocol to run queries on Presto.
|
5
5
|
|
6
6
|
* [Presto, a distributed SQL query engine for big data](https://github.com/facebook/presto)
|
7
7
|
|
8
|
-
|
8
|
+
You can use any PostgreSQL clients (see also *Limitation* section):
|
9
9
|
|
10
10
|
* `psql` command
|
11
11
|
* [PostgreSQL ODBC driver](http://psqlodbc.projects.pgfoundry.org/)
|
@@ -16,6 +16,9 @@ Prestogres also offers password-based authentication and SSL.
|
|
16
16
|
|
17
17
|
## How it works?
|
18
18
|
|
19
|
+
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.
|
21
|
+
|
19
22
|
```
|
20
23
|
PostgreSQL protocol Presto protocol (HTTP)
|
21
24
|
/ /
|
@@ -37,6 +40,7 @@ Prestogres also offers password-based authentication and SSL.
|
|
37
40
|
|
38
41
|
* Extended query is not supported ([PostgreSQL Frontend/Backend Protocol](http://www.postgresql.org/docs/9.3/static/protocol.html))
|
39
42
|
* ODBC driver needs to set **UseServerSidePrepare=0** (Server side prepare: no) property
|
43
|
+
* ODBC driver needs to use "Unicode" mode
|
40
44
|
* JDBC driver needs to set **protocolVersion=2** property
|
41
45
|
* DECLARE/FETCH is not supported
|
42
46
|
|
@@ -46,41 +50,38 @@ Prestogres also offers password-based authentication and SSL.
|
|
46
50
|
$ gem install prestogres --no-ri --no-rdoc
|
47
51
|
```
|
48
52
|
|
49
|
-
Prestogres package installs patched pgpool-II but doesn't install PostgreSQL. You need to install PostgreSQL
|
53
|
+
Prestogres package installs patched pgpool-II but doesn't install PostgreSQL. You need to install PostgreSQL >= 9.3 (with python support) separately.
|
50
54
|
|
51
55
|
* If you don't have **gem** command, install Ruby >= 1.9.0 first
|
52
56
|
* If installation failed, you may need to install following packages using apt or yum:
|
53
57
|
* basic toolchain (gcc, make, etc.)
|
54
|
-
* OpenSSL (Debian/Ubuntu: **libssl-dev**, RedHat/CentOS: **openssl-dev**)
|
55
|
-
* PostgreSQL server (Debian/Ubuntu: **postgresql-server-dev**, RedHat/CentOS: **postgresql-devel**)
|
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**)
|
60
|
+
|
61
|
+
### Installation FAQ
|
62
|
+
|
56
63
|
|
57
64
|
## Runing servers
|
58
65
|
|
59
|
-
You need to run 2 server programs: pgpool-II and PostgreSQL.
|
66
|
+
You need to run 2 server programs: pgpool-II and PostgreSQL.
|
67
|
+
You can use `prestogres` command to setup & run them as following:
|
60
68
|
|
61
|
-
|
62
|
-
|
69
|
+
```sh
|
70
|
+
# 1. Create a data directory:
|
63
71
|
$ prestogres -D pgdata setup
|
64
|
-
```
|
65
72
|
|
66
|
-
2. Configure
|
67
|
-
```
|
73
|
+
# 2. Configure presto_server and presto_catalog parameters at least in pgpool.conf file:
|
68
74
|
$ vi ./pgdata/pgpool/pgpool.conf
|
69
|
-
```
|
70
75
|
|
71
|
-
3. Run patched pgpool-II:
|
72
|
-
```
|
76
|
+
# 3. Run patched pgpool-II:
|
73
77
|
$ prestogres -D pgdata pgpool
|
74
|
-
```
|
75
78
|
|
76
|
-
4. Run PostgreSQL:
|
77
|
-
```
|
79
|
+
# 4. Run PostgreSQL:
|
78
80
|
$ prestogres -D pgdata pg_ctl start
|
79
|
-
```
|
80
81
|
|
81
|
-
5. Finally, you can connect to pgpool-II using `psql` command:
|
82
|
-
```
|
82
|
+
# 5. Finally, you can connect to pgpool-II using `psql` command:
|
83
83
|
$ psql -h localhost -p 9900 -U pg postgres
|
84
|
+
> SELECT * FROM sys.node;
|
84
85
|
```
|
85
86
|
|
86
87
|
If configuration is correct, you can run `SELECT * FROM sys.node;` query. Otherwise, see log files in **./pgdata/log/** directory.
|
@@ -111,7 +112,6 @@ Following parameters are unique to Prestogres:
|
|
111
112
|
|
112
113
|
* **presto_server**: Default address:port of Presto server.
|
113
114
|
* **presto_catalog**: Default catalog (connector) name of Presto such as `hive-cdh4`, `hive-hadoop1`, etc.
|
114
|
-
* **presto_schema**: Default schema name of Presto. You can read other schemas by qualified name like `FROM myschema.table1`
|
115
115
|
* **presto_external_auth_prog**: Default path to an external authentication program used by `prestogres_external` authentication moethd. See following Authentication section for details.
|
116
116
|
|
117
117
|
You can overwrite these parameters for each connecting users. See also following *pool_hba.conf* section.
|
@@ -124,10 +124,10 @@ See [sample pool_hba.conf file](https://github.com/treasure-data/prestogres/blob
|
|
124
124
|
|
125
125
|
```conf
|
126
126
|
# TYPE DATABASE USER CIDR-ADDRESS METHOD OPTIONS
|
127
|
-
host
|
128
|
-
host
|
129
|
-
host altdb pg 0.0.0.0/0 prestogres_md5 server:localhost:8190,
|
130
|
-
host all all 0.0.0.0/0 prestogres_external auth_prog:/opt/prestogres/auth.py
|
127
|
+
host postgres pg 127.0.0.1/32 trust
|
128
|
+
host postgres pg 127.0.0.1/32,192.168.0.0/16 prestogres_md5
|
129
|
+
host altdb pg 0.0.0.0/0 prestogres_md5 server:localhost:8190,pg_database:postgres
|
130
|
+
host all all 0.0.0.0/0 prestogres_external auth_prog:/opt/prestogres/auth.py,pg_database:postgres,pg_user:pg
|
131
131
|
```
|
132
132
|
|
133
133
|
See also *Creating database* section.
|
@@ -145,8 +145,8 @@ In pool_hba.conf file, you can set following options to OPTIONS field:
|
|
145
145
|
|
146
146
|
* **server**: Address:port of Presto server, which overwrites `presto_servers` parameter in pgpool.conf.
|
147
147
|
* **catalog**: Catalog (connector) name of Presto, which overwrites `presto_catalog` parameter in pgpool.conf.
|
148
|
-
* **schema**:
|
149
|
-
* **user**: User name to run queries on Presto. By default, Prestogres uses the same user name used to login to pgpool-II.
|
148
|
+
* **schema**: Default schema name of Presto. By default, Prestogres uses the same name with the database name used to login to pgpool-II. Following `pg_database` parameter doesn't overwrite affect this parameter.
|
149
|
+
* **user**: User name to run queries on Presto. By default, Prestogres uses the same user name used to login to pgpool-II. Following `pg_user` parameter doesn't overwrite affect this parameter.
|
150
150
|
* **pg_database**: Overwrite database to connect to PostgreSQL.
|
151
151
|
* **pg_user**: Overwrite user name to connect to PostgreSQL.
|
152
152
|
|
@@ -223,7 +223,7 @@ commands:
|
|
223
223
|
|
224
224
|
## Development
|
225
225
|
|
226
|
-
To install git HEAD, use following
|
226
|
+
To install git HEAD, use following commands to build:
|
227
227
|
|
228
228
|
```sh
|
229
229
|
# 1. clone prestogres repository:
|
@@ -239,7 +239,6 @@ $ bundle
|
|
239
239
|
$ bundle exec rake
|
240
240
|
|
241
241
|
# 4. install the created package:
|
242
|
-
$ gem install --no-ri --no-rdoc pkg/prestogres
|
242
|
+
$ gem install --no-ri --no-rdoc pkg/prestogres-*.gem
|
243
243
|
# if this command failed, you may need to install toolchain (gcc, etc.) to build pgpool-II
|
244
244
|
```
|
245
|
-
|
data/Rakefile
CHANGED
@@ -4,9 +4,10 @@ Bundler::GemHelper.install_tasks
|
|
4
4
|
require 'rake/extensiontask'
|
5
5
|
|
6
6
|
spec = eval File.read("prestogres.gemspec")
|
7
|
-
Rake::ExtensionTask.new('
|
7
|
+
Rake::ExtensionTask.new('prestogres_config', spec) do |ext|
|
8
8
|
ext.cross_compile = true
|
9
|
-
ext.lib_dir =
|
9
|
+
ext.lib_dir = 'lib'
|
10
|
+
ext.ext_dir = 'ext'
|
10
11
|
#ext.cross_platform = 'i386-mswin32'
|
11
12
|
end
|
12
13
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/bin/prestogres
CHANGED
@@ -7,7 +7,7 @@ config = {
|
|
7
7
|
}
|
8
8
|
|
9
9
|
def usage(error=nil)
|
10
|
-
puts "usage: #{File.basename($0)} -D <data dir> <command>"
|
10
|
+
puts "usage: #{File.basename($0)} -D <data dir> [-P <postgresql bin path>] <command>"
|
11
11
|
puts "commands:"
|
12
12
|
puts " setup setup <data dir>"
|
13
13
|
puts " pgpool start pgpool as a daemon process"
|
@@ -38,6 +38,8 @@ ARGV.each_with_index do |a,i|
|
|
38
38
|
case a
|
39
39
|
when "-D"
|
40
40
|
config_key = :data_dir
|
41
|
+
when "-P"
|
42
|
+
config_key = :bin_path
|
41
43
|
when "-h", "-?", "--help"
|
42
44
|
config[:command] = :help
|
43
45
|
when "setup"
|
@@ -73,6 +75,8 @@ setup_params = {
|
|
73
75
|
unix_socket_directory: nil,
|
74
76
|
}
|
75
77
|
|
78
|
+
ENV['PATH'] = "#{ENV['PATH']}:#{config[:bin_path]}"
|
79
|
+
|
76
80
|
case config[:command]
|
77
81
|
when :help
|
78
82
|
usage nil
|
@@ -87,15 +91,11 @@ when :setup
|
|
87
91
|
|
88
92
|
require "erb"
|
89
93
|
require "fileutils"
|
94
|
+
require "shellwords"
|
90
95
|
|
91
|
-
def
|
92
|
-
|
93
|
-
|
94
|
-
system *cmdline
|
95
|
-
else
|
96
|
-
puts "setup> #{cmdline}"
|
97
|
-
system cmdline
|
98
|
-
end
|
96
|
+
def setup_cmd(cmdline)
|
97
|
+
puts "setup> #{cmdline}"
|
98
|
+
system cmdline
|
99
99
|
unless $?.success?
|
100
100
|
puts "******************"
|
101
101
|
puts "** setup failed **"
|
@@ -114,44 +114,84 @@ when :setup
|
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
117
|
+
def se(raw)
|
118
|
+
Shellwords.escape(raw)
|
119
|
+
end
|
120
|
+
|
121
|
+
`which initdb && which postgres`
|
122
|
+
unless $?.success?
|
123
|
+
puts ""
|
124
|
+
puts "Can't find initdb or postgres command. Check following items:"
|
125
|
+
puts ""
|
126
|
+
puts " * You need to install PostgreSQL server. Use apt or yum to install postgresql package."
|
127
|
+
puts " * You need to set -P option to specify path to the commands. Example:"
|
128
|
+
puts ""
|
129
|
+
puts " $ prestogres -D data_dir -P /usr/lib/postgresql/9.1/bin setup"
|
130
|
+
puts ""
|
131
|
+
exit 1
|
132
|
+
end
|
133
|
+
|
134
|
+
# src files
|
117
135
|
config_src_dir = File.join(File.dirname(__FILE__), "..", "config")
|
136
|
+
postgresql_conf_path = File.join(config_src_dir, "postgresql.conf")
|
118
137
|
pgsql_dir = File.join(File.dirname(__FILE__), "..", "pgsql")
|
138
|
+
setup_sql_path = File.join(pgsql_dir, "setup.sql")
|
119
139
|
|
120
140
|
puts "Setting up '#{config[:data_dir]}'..."
|
121
141
|
data_dir = File.expand_path(config[:data_dir])
|
122
142
|
|
123
|
-
@config = setup_params # used by erb files
|
124
|
-
@config[:pgpool_pid_file] = File.join(data_dir, "run", "pgpool.pid")
|
125
|
-
@config[:pgpool_status_dir] = File.join(data_dir, "run")
|
126
|
-
@config[:unix_socket_directory] = File.join(data_dir, "run")
|
127
|
-
|
128
143
|
# log & socket dirs
|
129
|
-
|
130
|
-
|
144
|
+
log_dir = File.join(data_dir, "log")
|
145
|
+
run_dir = File.join(data_dir, "run")
|
146
|
+
FileUtils.mkdir_p log_dir
|
147
|
+
FileUtils.mkdir_p run_dir
|
148
|
+
|
149
|
+
pgpool_dir = File.join(data_dir, "pgpool")
|
150
|
+
postgres_dir = File.join(data_dir, "postgres")
|
151
|
+
|
152
|
+
@config = setup_params # used by erb files
|
153
|
+
@config[:pgpool_pid_file] = File.join(run_dir, "pgpool.pid")
|
154
|
+
@config[:pgpool_status_dir] = run_dir
|
155
|
+
@config[:unix_socket_directory] = run_dir
|
131
156
|
|
132
157
|
# pgpool
|
133
|
-
FileUtils.mkdir_p
|
158
|
+
FileUtils.mkdir_p pgpool_dir
|
134
159
|
%w[pcp.conf.sample pgpool.conf pool_hba.conf pool_passwd].each do |fname|
|
135
|
-
|
136
|
-
|
160
|
+
# run ERB for each config files
|
161
|
+
dst_path = File.join(pgpool_dir, fname)
|
162
|
+
src_path = File.join(config_src_dir, fname)
|
163
|
+
File.open(dst_path, "w") {|f|
|
164
|
+
f.write ERB.new(File.read(src_path)).result
|
137
165
|
}
|
138
166
|
end
|
139
167
|
|
140
168
|
# postgresql
|
141
|
-
|
169
|
+
# 1. initdb postgres_dir
|
170
|
+
setup_cmd "initdb -U pg --no-locale -E UNICODE #{se(postgres_dir)}"
|
142
171
|
File.open(File.join(data_dir, "postgres", "postgresql.conf"), "a") {|f|
|
143
|
-
f.write ERB.new(File.read(
|
172
|
+
f.write ERB.new(File.read(postgresql_conf_path)).result
|
144
173
|
}
|
145
174
|
|
146
|
-
#
|
147
|
-
|
175
|
+
# 2. postgres -D postgres_dir -c listen_addresses=
|
176
|
+
# start postgres without listening on TCP
|
177
|
+
postgres_cmd = "postgres -D #{se(postgres_dir)} -c listen_addresses="
|
178
|
+
puts "setup> #{postgres_cmd}"
|
179
|
+
pid = spawn postgres_cmd
|
180
|
+
|
181
|
+
# 3. psql < pgsql/setup.sql
|
148
182
|
begin
|
149
|
-
|
183
|
+
# waits for spinup of postgres
|
184
|
+
sleep 2
|
185
|
+
puts "initializing database..."
|
186
|
+
sleep 8
|
150
187
|
|
151
|
-
|
188
|
+
psql_cmd = "psql -h #{se(@config[:unix_socket_directory])} -p #{@config[:backend_port]} -U pg postgres"
|
189
|
+
setup_cmd "#{psql_cmd} < #{se(setup_sql_path)}"
|
152
190
|
ensure
|
153
|
-
|
191
|
+
# 4. shutdown postgres
|
192
|
+
Process.kill(:SIGQUIT, pid)
|
154
193
|
end
|
194
|
+
Process.waitpid(pid)
|
155
195
|
|
156
196
|
puts <<EOF
|
157
197
|
|
@@ -203,7 +243,9 @@ when :passwd
|
|
203
243
|
binary = File.join(prefix, "bin", 'pg_md5')
|
204
244
|
|
205
245
|
data_dir = config[:data_dir]
|
206
|
-
|
246
|
+
pgpool_conf_path = File.join(data_dir, "pgpool", "pgpool.conf")
|
247
|
+
|
248
|
+
args = ["-f", pgpool_conf_path, "-p", "-m", "-u"].concat(args)
|
207
249
|
|
208
250
|
puts "#{binary} #{args.join(' ')}"
|
209
251
|
system binary, *args
|
data/config/postgresql.conf
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
port = <%= @config[:backend_port] %>
|
2
|
-
|
2
|
+
unix_socket_directories = '<%= @config[:unix_socket_directory] %>'
|
data/ext/depend
CHANGED
@@ -1,8 +1,16 @@
|
|
1
1
|
# vim: ft=make noexpandtab
|
2
2
|
|
3
|
-
|
3
|
+
# normalize rubyarchdir which is not available depending on rake or gem
|
4
|
+
narchdir = $(if $(rubyarchdir),$(rubyarchdir),$(RUBYARCHDIR))
|
4
5
|
|
5
|
-
|
6
|
+
# hack for RubyGems >= 1.8.26
|
7
|
+
final_archdir = $(if $(findstring .gem.,$(narchdir)),$(dir $(narchdir)),$(narchdir))
|
8
|
+
|
9
|
+
INSTALL_DATA = $(realpath $(final_archdir))/prestogres
|
10
|
+
|
11
|
+
PGPOOL2_PATH = $(realpath $(srcdir)/../pgpool2)
|
12
|
+
|
13
|
+
CFLAGS += -DPRESTOGRES_PREFIX="\"$(INSTALL_DATA)\""
|
6
14
|
|
7
15
|
all: pgpool2
|
8
16
|
|
@@ -12,10 +20,10 @@ install: install-pgpool2
|
|
12
20
|
|
13
21
|
pgpool2:
|
14
22
|
mkdir -p build
|
15
|
-
cd build &&
|
16
|
-
CFLAGS="-I$(
|
23
|
+
cd build && "$(PGPOOL2_PATH)/configure" \
|
24
|
+
CFLAGS="-I$(PGPOOL2_PATH)" \
|
17
25
|
--with-openssl \
|
18
|
-
--prefix="$(
|
26
|
+
--prefix="$(INSTALL_DATA)"
|
19
27
|
cd build && make sysconfdir=/opt/prestogres/etc
|
20
28
|
|
21
29
|
clean-pgpool2:
|
data/pgpool2/child.c
CHANGED
@@ -295,6 +295,9 @@ void do_child(int unix_fd, int inet_fd)
|
|
295
295
|
goto retry_startup;
|
296
296
|
}
|
297
297
|
|
298
|
+
/* this should run before ClientAuthentication */
|
299
|
+
pool_prestogres_set_defaults(sp);
|
300
|
+
|
298
301
|
if (pool_config->enable_pool_hba)
|
299
302
|
{
|
300
303
|
/*
|
@@ -332,9 +335,6 @@ void do_child(int unix_fd, int inet_fd)
|
|
332
335
|
}
|
333
336
|
}
|
334
337
|
|
335
|
-
/* this should run after ClientAuthentication */
|
336
|
-
pool_prestogres_init_session(frontend);
|
337
|
-
|
338
338
|
/*
|
339
339
|
* Ok, negotiation with frontend has been done. Let's go to the
|
340
340
|
* next step. Connect to backend if there's no existing
|
data/pgpool2/pool.h
CHANGED
@@ -652,6 +652,6 @@ extern const char* presto_external_auth_prog;
|
|
652
652
|
int send_md5auth_request(POOL_CONNECTION *frontend, int protoMajor, char *salt);
|
653
653
|
int read_password_packet(POOL_CONNECTION *frontend, int protoMajor, char *password, int *pwdSize);
|
654
654
|
|
655
|
-
void
|
655
|
+
void pool_prestogres_set_defaults(StartupPacket *sp);
|
656
656
|
|
657
657
|
#endif /* POOL_H */
|
data/pgpool2/pool_config.c
CHANGED
@@ -1896,7 +1896,6 @@ int pool_init_config(void)
|
|
1896
1896
|
|
1897
1897
|
pool_config->presto_server = "";
|
1898
1898
|
pool_config->presto_catalog = "";
|
1899
|
-
pool_config->presto_schema = "default";
|
1900
1899
|
pool_config->presto_external_auth_prog = NULL;
|
1901
1900
|
|
1902
1901
|
pool_config->replication_mode = 0;
|
@@ -2599,24 +2598,6 @@ int pool_get_config(char *confpath, POOL_CONFIG_CONTEXT context)
|
|
2599
2598
|
}
|
2600
2599
|
pool_config->presto_catalog = str;
|
2601
2600
|
}
|
2602
|
-
else if (!strcmp(key, "presto_schema") && CHECK_CONTEXT(INIT_CONFIG, context))
|
2603
|
-
{
|
2604
|
-
char *str;
|
2605
|
-
|
2606
|
-
if (token != POOL_STRING && token != POOL_UNQUOTED_STRING && token != POOL_KEY)
|
2607
|
-
{
|
2608
|
-
PARSE_ERROR();
|
2609
|
-
fclose(fd);
|
2610
|
-
return(-1);
|
2611
|
-
}
|
2612
|
-
str = extract_string(yytext, token);
|
2613
|
-
if (str == NULL)
|
2614
|
-
{
|
2615
|
-
fclose(fd);
|
2616
|
-
return(-1);
|
2617
|
-
}
|
2618
|
-
pool_config->presto_schema = str;
|
2619
|
-
}
|
2620
2601
|
else if (!strcmp(key, "presto_external_auth_prog") && CHECK_CONTEXT(INIT_CONFIG, context))
|
2621
2602
|
{
|
2622
2603
|
char *str;
|
data/pgpool2/pool_hba.c
CHANGED
@@ -1709,23 +1709,23 @@ static POOL_STATUS pool_prestogres_hba_auth_external(POOL_CONNECTION *frontend)
|
|
1709
1709
|
return POOL_CONTINUE;
|
1710
1710
|
}
|
1711
1711
|
|
1712
|
-
void
|
1712
|
+
void pool_prestogres_set_defaults(StartupPacket *sp)
|
1713
1713
|
{
|
1714
1714
|
if (presto_server == NULL) {
|
1715
1715
|
presto_server = pool_config->presto_server;
|
1716
1716
|
}
|
1717
1717
|
if (presto_user == NULL) {
|
1718
|
-
presto_user =
|
1718
|
+
presto_user = strdup(sp->user);
|
1719
1719
|
}
|
1720
1720
|
if (presto_catalog == NULL) {
|
1721
1721
|
presto_catalog = pool_config->presto_catalog;
|
1722
1722
|
}
|
1723
1723
|
if (presto_schema == NULL) {
|
1724
|
-
presto_schema =
|
1724
|
+
presto_schema = strdup(sp->database);
|
1725
1725
|
}
|
1726
|
-
pool_debug("
|
1727
|
-
pool_debug("
|
1728
|
-
pool_debug("
|
1729
|
-
pool_debug("
|
1726
|
+
pool_debug("pool_prestogres_set_defaults: presto_server: %s", presto_server);
|
1727
|
+
pool_debug("pool_prestogres_set_defaults: presto_user: %s", presto_user);
|
1728
|
+
pool_debug("pool_prestogres_set_defaults: presto_catalog: %s", presto_catalog);
|
1729
|
+
pool_debug("pool_prestogres_set_defaults: presto_schema: %s", presto_schema);
|
1730
1730
|
}
|
1731
1731
|
|
@@ -404,7 +404,31 @@ static void run_and_rewrite_system_catalog_query(POOL_SESSION_CONTEXT* session_c
|
|
404
404
|
POOL_CONNECTION_POOL *backend = session_context->backend;
|
405
405
|
con = CONNECTION(backend, session_context->load_balance_node_id);
|
406
406
|
|
407
|
-
/* build query */
|
407
|
+
/* build SET query */
|
408
|
+
buffer = rewrite_query_string_buffer;
|
409
|
+
bufend = buffer + sizeof(rewrite_query_string_buffer);
|
410
|
+
|
411
|
+
buffer = strcpy_capped(buffer, bufend - buffer, "set search_path to E'");
|
412
|
+
buffer = strcpy_capped_escaped(buffer, bufend - buffer, presto_schema, "'\\");
|
413
|
+
buffer = strcpy_capped(buffer, bufend - buffer, "'");
|
414
|
+
|
415
|
+
if (buffer == NULL) {
|
416
|
+
rewrite_error_query(query_context, "schema name too long", NULL);
|
417
|
+
return;
|
418
|
+
}
|
419
|
+
|
420
|
+
/* run SET query */
|
421
|
+
status = do_query_or_get_error_message(con,
|
422
|
+
rewrite_query_string_buffer, &res, MAJOR(backend), &errmsg, &errcode);
|
423
|
+
free_select_result(res);
|
424
|
+
|
425
|
+
if (errmsg != NULL) {
|
426
|
+
rewrite_error_query(query_context, errmsg, errcode);
|
427
|
+
} else if (status != POOL_CONTINUE) {
|
428
|
+
rewrite_error_query(query_context, "Unknown execution error", NULL);
|
429
|
+
}
|
430
|
+
|
431
|
+
/* build run_system_catalog_as_temp_table query */
|
408
432
|
buffer = rewrite_query_string_buffer;
|
409
433
|
bufend = buffer + sizeof(rewrite_query_string_buffer);
|
410
434
|
|
@@ -415,6 +439,8 @@ static void run_and_rewrite_system_catalog_query(POOL_SESSION_CONTEXT* session_c
|
|
415
439
|
buffer = strcpy_capped(buffer, bufend - buffer, "', E'");
|
416
440
|
buffer = strcpy_capped_escaped(buffer, bufend - buffer, presto_catalog, "'\\");
|
417
441
|
buffer = strcpy_capped(buffer, bufend - buffer, "', E'");
|
442
|
+
buffer = strcpy_capped_escaped(buffer, bufend - buffer, presto_schema, "'\\");
|
443
|
+
buffer = strcpy_capped(buffer, bufend - buffer, "', E'");
|
418
444
|
buffer = strcpy_capped_escaped(buffer, bufend - buffer, PRESTO_RESULT_TABLE_NAME, "'\\");
|
419
445
|
buffer = strcpy_capped(buffer, bufend - buffer, "', E'");
|
420
446
|
buffer = strcpy_capped_escaped(buffer, bufend - buffer, query_context->original_query, "'\\");
|
@@ -425,7 +451,7 @@ static void run_and_rewrite_system_catalog_query(POOL_SESSION_CONTEXT* session_c
|
|
425
451
|
return;
|
426
452
|
}
|
427
453
|
|
428
|
-
/* run query */
|
454
|
+
/* run run_system_catalog_as_temp_table query */
|
429
455
|
status = do_query_or_get_error_message(con,
|
430
456
|
rewrite_query_string_buffer, &res, MAJOR(backend), &errmsg, &errcode);
|
431
457
|
free_select_result(res);
|
data/pgsql/prestogres.py
CHANGED
@@ -6,7 +6,7 @@ import time
|
|
6
6
|
# convert Presto query result type to PostgreSQL type
|
7
7
|
def _pg_result_type(presto_type):
|
8
8
|
if presto_type == "varchar":
|
9
|
-
return "
|
9
|
+
return "varchar(255)"
|
10
10
|
elif presto_type == "bigint":
|
11
11
|
return "bigint"
|
12
12
|
elif presto_type == "boolean":
|
@@ -19,7 +19,7 @@ def _pg_result_type(presto_type):
|
|
19
19
|
# convert Presto type to PostgreSQL type
|
20
20
|
def _pg_table_type(presto_type):
|
21
21
|
if presto_type == "varchar":
|
22
|
-
return "varchar(
|
22
|
+
return "varchar(255)"
|
23
23
|
elif presto_type == "bigint":
|
24
24
|
return "bigint"
|
25
25
|
elif presto_type == "boolean":
|
@@ -30,7 +30,7 @@ def _pg_table_type(presto_type):
|
|
30
30
|
raise Exception("unknown table column type: " + plpy.quote_ident(presto_type))
|
31
31
|
|
32
32
|
# build CREATE TEMPORARY TABLE statement
|
33
|
-
def _build_create_temp_table_sql(table_name, column_names, column_types
|
33
|
+
def _build_create_temp_table_sql(table_name, column_names, column_types):
|
34
34
|
create_sql = "create temporary table %s (\n " % plpy.quote_ident(table_name)
|
35
35
|
|
36
36
|
first = True
|
@@ -44,9 +44,26 @@ def _build_create_temp_table_sql(table_name, column_names, column_types, not_nul
|
|
44
44
|
create_sql += " "
|
45
45
|
create_sql += column_type
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
47
|
+
create_sql += "\n)"
|
48
|
+
return create_sql
|
49
|
+
|
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))
|
53
|
+
|
54
|
+
first = True
|
55
|
+
for column_name, column_type, not_null in zip(column_names, column_types, not_nulls):
|
56
|
+
if first:
|
57
|
+
first = False
|
58
|
+
else:
|
59
|
+
create_sql += ",\n "
|
60
|
+
|
61
|
+
create_sql += plpy.quote_ident(column_name)
|
62
|
+
create_sql += " "
|
63
|
+
create_sql += column_type
|
64
|
+
|
65
|
+
if not_null:
|
66
|
+
create_sql += " not null"
|
50
67
|
|
51
68
|
create_sql += "\n)"
|
52
69
|
return create_sql
|
@@ -100,21 +117,28 @@ class SchemaCache(object):
|
|
100
117
|
self.server = None
|
101
118
|
self.user = None
|
102
119
|
self.catalog = None
|
120
|
+
self.schema = None
|
103
121
|
self.schema_names = None
|
104
122
|
self.statements = None
|
105
123
|
self.expire_time = None
|
124
|
+
self.query_cache = {}
|
106
125
|
|
107
|
-
def is_cached(self, server, user, catalog, current_time):
|
108
|
-
return self.server == server and self.user == user
|
126
|
+
def is_cached(self, server, user, catalog, schema, current_time):
|
127
|
+
return self.server == server and self.user == user \
|
128
|
+
and self.catalog == catalog and self.schema == schema \
|
109
129
|
and self.statements is not None and current_time < self.expire_time
|
110
130
|
|
111
|
-
def set_cache(self, server, user, catalog, schema_names, statements, expire_time):
|
131
|
+
def set_cache(self, server, user, catalog, schema, schema_names, statements, expire_time):
|
112
132
|
self.server = server
|
113
133
|
self.user = user
|
114
134
|
self.catalog = catalog
|
135
|
+
self.schema = schema
|
115
136
|
self.schema_names = schema_names
|
116
137
|
self.statements = statements
|
117
138
|
self.expire_time = expire_time
|
139
|
+
self.query_cache = {}
|
140
|
+
|
141
|
+
QueryResult = namedtuple("QueryResult", ("column_names", "column_types", "result"))
|
118
142
|
|
119
143
|
OidToTypeNameMapping = {}
|
120
144
|
|
@@ -171,14 +195,15 @@ def run_presto_as_temp_table(server, user, catalog, schema, result_table, query)
|
|
171
195
|
e.__class__.__module__ = "__main__"
|
172
196
|
raise
|
173
197
|
|
174
|
-
def run_system_catalog_as_temp_table(server, user, catalog, result_table, query):
|
198
|
+
def run_system_catalog_as_temp_table(server, user, catalog, schema, result_table, query):
|
175
199
|
try:
|
176
|
-
client = presto_client.Client(server=server, user=user, catalog=catalog, schema=
|
200
|
+
client = presto_client.Client(server=server, user=user, catalog=catalog, schema=schema)
|
177
201
|
|
178
202
|
# create SQL statements which put data to system catalogs
|
179
|
-
if SchemaCacheEntry.is_cached(server, user, catalog, time.time()):
|
203
|
+
if SchemaCacheEntry.is_cached(server, user, catalog, schema, time.time()):
|
180
204
|
schema_names = SchemaCacheEntry.schema_names
|
181
205
|
statements = SchemaCacheEntry.statements
|
206
|
+
query_cache = SchemaCacheEntry.query_cache
|
182
207
|
|
183
208
|
else:
|
184
209
|
# get table list
|
@@ -223,57 +248,69 @@ def run_system_catalog_as_temp_table(server, user, catalog, result_table, query)
|
|
223
248
|
column_types.append(_pg_table_type(column.type))
|
224
249
|
not_nulls.append(not column.nullable)
|
225
250
|
|
226
|
-
create_sql =
|
251
|
+
create_sql = _build_create_table_sql(schema_name, table_name, column_names, column_types, not_nulls)
|
227
252
|
statements.append(create_sql)
|
228
253
|
|
229
|
-
# cache expires after
|
230
|
-
SchemaCacheEntry.set_cache(server, user, catalog, schema_names, statements, time.time() +
|
254
|
+
# cache expires after 60 seconds
|
255
|
+
SchemaCacheEntry.set_cache(server, user, catalog, schema, schema_names, statements, time.time() + 60)
|
256
|
+
query_cache = {}
|
231
257
|
|
232
|
-
|
233
|
-
subxact = plpy.subtransaction()
|
234
|
-
subxact.enter()
|
235
|
-
try:
|
236
|
-
# delete all schemas excepting prestogres_catalog
|
237
|
-
sql = "select n.nspname as schema_name from pg_catalog.pg_namespace n" \
|
238
|
-
" where n.nspname not in ('prestogres_catalog', 'pg_catalog', 'information_schema', 'public')" \
|
239
|
-
" and n.nspname !~ '^pg_toast'"
|
240
|
-
for row in plpy.cursor(sql):
|
241
|
-
plpy.execute("drop schema %s cascade" % plpy.quote_ident(row["schema_name"]))
|
242
|
-
|
243
|
-
# delete all tables in prestogres_catalog
|
244
|
-
# relkind = 'r' takes only tables and skip views, indexes, etc.
|
245
|
-
sql = "select n.nspname as schema_name, c.relname as table_name from pg_catalog.pg_class c" \
|
246
|
-
" left join pg_catalog.pg_namespace n on n.oid = c.relnamespace" \
|
247
|
-
" where c.relkind in ('r')" \
|
248
|
-
" and n.nspname in ('prestogres_catalog')" \
|
249
|
-
" and n.nspname !~ '^pg_toast'"
|
250
|
-
for row in plpy.cursor(sql):
|
251
|
-
plpy.execute("drop table %s.%s" % (plpy.quote_ident(row["schema_name"]), plpy.quote_ident(row["table_name"])))
|
252
|
-
|
253
|
-
# create schemas
|
254
|
-
for schema_name in schema_names:
|
255
|
-
try:
|
256
|
-
plpy.execute("create schema %s" % plpy.quote_ident(schema_name))
|
257
|
-
except:
|
258
|
-
# ignore error
|
259
|
-
pass
|
260
|
-
|
261
|
-
# create tables
|
262
|
-
for statement in statements:
|
263
|
-
plpy.execute(statement)
|
264
|
-
|
265
|
-
# run the actual query
|
266
|
-
metadata = plpy.execute(query)
|
267
|
-
result = map(lambda row: row.values(), metadata)
|
258
|
+
query_result = query_cache.get(query)
|
268
259
|
|
269
|
-
|
270
|
-
|
271
|
-
|
260
|
+
if query_result:
|
261
|
+
column_names = query_result.column_names
|
262
|
+
column_types = query_result.column_types
|
263
|
+
result = query_result.result
|
272
264
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
265
|
+
else:
|
266
|
+
# enter subtransaction to rollback tables right after running the query
|
267
|
+
subxact = plpy.subtransaction()
|
268
|
+
subxact.enter()
|
269
|
+
try:
|
270
|
+
# delete all schemas excepting prestogres_catalog
|
271
|
+
sql = "select n.nspname as schema_name from pg_catalog.pg_namespace n" \
|
272
|
+
" where n.nspname not in ('prestogres_catalog', 'pg_catalog', 'information_schema', 'public')" \
|
273
|
+
" and n.nspname !~ '^pg_toast'"
|
274
|
+
for row in plpy.cursor(sql):
|
275
|
+
plpy.execute("drop schema %s cascade" % plpy.quote_ident(row["schema_name"]))
|
276
|
+
|
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
|
288
|
+
for schema_name in schema_names:
|
289
|
+
try:
|
290
|
+
plpy.execute("create schema %s" % plpy.quote_ident(schema_name))
|
291
|
+
except:
|
292
|
+
# ignore error
|
293
|
+
pass
|
294
|
+
|
295
|
+
# create tables
|
296
|
+
for statement in statements:
|
297
|
+
plpy.execute(statement)
|
298
|
+
|
299
|
+
# run the actual query
|
300
|
+
metadata = plpy.execute(query)
|
301
|
+
column_names = metadata.colnames()
|
302
|
+
column_type_oids = metadata.coltypes()
|
303
|
+
result = map(lambda row: map(row.get, column_names), metadata)
|
304
|
+
|
305
|
+
# table schema
|
306
|
+
oid_to_type_name = _load_oid_to_type_name_mapping(column_type_oids)
|
307
|
+
column_types = map(oid_to_type_name.get, column_type_oids)
|
308
|
+
|
309
|
+
query_cache[query] = QueryResult(column_names, column_types, result)
|
310
|
+
|
311
|
+
finally:
|
312
|
+
# rollback subtransaction
|
313
|
+
subxact.exit("rollback subtransaction", None, None)
|
277
314
|
|
278
315
|
create_sql = _build_create_temp_table_sql(result_table, column_names, column_types)
|
279
316
|
insert_sql, values_sql_format = _build_insert_into_sql(result_table, column_names)
|
data/pgsql/setup.sql
CHANGED
@@ -19,10 +19,11 @@ create or replace function prestogres_catalog.run_system_catalog_as_temp_table(
|
|
19
19
|
"server" text,
|
20
20
|
"user" text,
|
21
21
|
"catalog" text,
|
22
|
+
"schema" text,
|
22
23
|
"table_name" text,
|
23
24
|
"query" text)
|
24
25
|
returns void as $$
|
25
26
|
import prestogres
|
26
|
-
prestogres.run_system_catalog_as_temp_table(server, user, catalog, table_name, query)
|
27
|
+
prestogres.run_system_catalog_as_temp_table(server, user, catalog, schema, table_name, query)
|
27
28
|
$$ language plpythonu;
|
28
29
|
|
metadata
CHANGED
@@ -1,55 +1,62 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prestogres
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
|
+
prerelease:
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Sadayuki Furuhashi
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2014-
|
12
|
+
date: 2014-02-07 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: bundler
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
16
18
|
requirements:
|
17
|
-
- -
|
19
|
+
- - ~>
|
18
20
|
- !ruby/object:Gem::Version
|
19
21
|
version: '1.0'
|
20
22
|
type: :development
|
21
23
|
prerelease: false
|
22
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
23
26
|
requirements:
|
24
|
-
- -
|
27
|
+
- - ~>
|
25
28
|
- !ruby/object:Gem::Version
|
26
29
|
version: '1.0'
|
27
30
|
- !ruby/object:Gem::Dependency
|
28
31
|
name: rake
|
29
32
|
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
30
34
|
requirements:
|
31
|
-
- -
|
35
|
+
- - ~>
|
32
36
|
- !ruby/object:Gem::Version
|
33
37
|
version: 0.9.2
|
34
38
|
type: :development
|
35
39
|
prerelease: false
|
36
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
37
42
|
requirements:
|
38
|
-
- -
|
43
|
+
- - ~>
|
39
44
|
- !ruby/object:Gem::Version
|
40
45
|
version: 0.9.2
|
41
46
|
- !ruby/object:Gem::Dependency
|
42
47
|
name: rake-compiler
|
43
48
|
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
44
50
|
requirements:
|
45
|
-
- -
|
51
|
+
- - ~>
|
46
52
|
- !ruby/object:Gem::Version
|
47
53
|
version: 0.8.3
|
48
54
|
type: :development
|
49
55
|
prerelease: false
|
50
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
51
58
|
requirements:
|
52
|
-
- -
|
59
|
+
- - ~>
|
53
60
|
- !ruby/object:Gem::Version
|
54
61
|
version: 0.8.3
|
55
62
|
description: Presto PostgreSQL protocol gateway
|
@@ -61,10 +68,9 @@ extensions:
|
|
61
68
|
- ext/extconf.rb
|
62
69
|
extra_rdoc_files: []
|
63
70
|
files:
|
64
|
-
-
|
71
|
+
- .gitignore
|
65
72
|
- ChangeLog
|
66
73
|
- Gemfile
|
67
|
-
- Gemfile.lock
|
68
74
|
- LICENSE
|
69
75
|
- NOTICE
|
70
76
|
- README.md
|
@@ -463,25 +469,32 @@ files:
|
|
463
469
|
homepage: https://github.com/treasure-data/prestogres
|
464
470
|
licenses:
|
465
471
|
- Apache 2.0
|
466
|
-
metadata: {}
|
467
472
|
post_install_message:
|
468
473
|
rdoc_options: []
|
469
474
|
require_paths:
|
470
475
|
- lib
|
471
476
|
required_ruby_version: !ruby/object:Gem::Requirement
|
477
|
+
none: false
|
472
478
|
requirements:
|
473
|
-
- -
|
479
|
+
- - ! '>='
|
474
480
|
- !ruby/object:Gem::Version
|
475
481
|
version: '0'
|
482
|
+
segments:
|
483
|
+
- 0
|
484
|
+
hash: -1048444669451031740
|
476
485
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
486
|
+
none: false
|
477
487
|
requirements:
|
478
|
-
- -
|
488
|
+
- - ! '>='
|
479
489
|
- !ruby/object:Gem::Version
|
480
490
|
version: '0'
|
491
|
+
segments:
|
492
|
+
- 0
|
493
|
+
hash: -1048444669451031740
|
481
494
|
requirements: []
|
482
495
|
rubyforge_project:
|
483
|
-
rubygems_version:
|
496
|
+
rubygems_version: 1.8.23
|
484
497
|
signing_key:
|
485
|
-
specification_version:
|
498
|
+
specification_version: 3
|
486
499
|
summary: Presto PostgreSQL protocol gateway
|
487
500
|
test_files: []
|
checksums.yaml
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
---
|
2
|
-
SHA1:
|
3
|
-
metadata.gz: fdbbafba90e2dde6d7340746b30d369eda073ed9
|
4
|
-
data.tar.gz: 57d91cbb2bb74c2ce53132ea9309eb9a0cb47e09
|
5
|
-
SHA512:
|
6
|
-
metadata.gz: f166e0435a473ec6930522f0a139733f24227a8b0045725fc1e89f4afda25c6fd589c2dc121a46e99c82f79bba135c601b42fec45651c66c185a0aaae6f52eb6
|
7
|
-
data.tar.gz: c351d1a8fe7c59cc7b9a2ec603935766141958df839d4f7179a9921e9410b2da513f6b05beab2f6b5d3d96be309542df876b01fce42f4021f0692d40ceb84652
|
data/Gemfile.lock
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
prestogres (0.1.0)
|
5
|
-
|
6
|
-
GEM
|
7
|
-
remote: https://rubygems.org/
|
8
|
-
specs:
|
9
|
-
rake (0.9.6)
|
10
|
-
rake-compiler (0.8.3)
|
11
|
-
rake
|
12
|
-
|
13
|
-
PLATFORMS
|
14
|
-
ruby
|
15
|
-
|
16
|
-
DEPENDENCIES
|
17
|
-
bundler (~> 1.0)
|
18
|
-
prestogres!
|
19
|
-
rake (~> 0.9.2)
|
20
|
-
rake-compiler (~> 0.8.3)
|