prestogres 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
# ![Prestogres](https://gist.github.com/frsyuki/8328440/raw/6c3a19b7132fbbf975155669f308854f70fff1e8/prestogres.png)
|
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)
|