sequel_core 1.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/CHANGELOG +1003 -0
- data/COPYING +18 -0
- data/README +81 -0
- data/Rakefile +176 -0
- data/bin/sequel +41 -0
- data/lib/sequel_core.rb +59 -0
- data/lib/sequel_core/adapters/adapter_skeleton.rb +68 -0
- data/lib/sequel_core/adapters/ado.rb +100 -0
- data/lib/sequel_core/adapters/db2.rb +158 -0
- data/lib/sequel_core/adapters/dbi.rb +126 -0
- data/lib/sequel_core/adapters/informix.rb +87 -0
- data/lib/sequel_core/adapters/jdbc.rb +108 -0
- data/lib/sequel_core/adapters/mysql.rb +269 -0
- data/lib/sequel_core/adapters/odbc.rb +145 -0
- data/lib/sequel_core/adapters/odbc_mssql.rb +93 -0
- data/lib/sequel_core/adapters/openbase.rb +90 -0
- data/lib/sequel_core/adapters/oracle.rb +99 -0
- data/lib/sequel_core/adapters/postgres.rb +519 -0
- data/lib/sequel_core/adapters/sqlite.rb +192 -0
- data/lib/sequel_core/array_keys.rb +296 -0
- data/lib/sequel_core/connection_pool.rb +152 -0
- data/lib/sequel_core/core_ext.rb +59 -0
- data/lib/sequel_core/core_sql.rb +191 -0
- data/lib/sequel_core/database.rb +433 -0
- data/lib/sequel_core/dataset.rb +409 -0
- data/lib/sequel_core/dataset/convenience.rb +321 -0
- data/lib/sequel_core/dataset/sequelizer.rb +354 -0
- data/lib/sequel_core/dataset/sql.rb +586 -0
- data/lib/sequel_core/exceptions.rb +45 -0
- data/lib/sequel_core/migration.rb +191 -0
- data/lib/sequel_core/model.rb +8 -0
- data/lib/sequel_core/pretty_table.rb +73 -0
- data/lib/sequel_core/schema.rb +8 -0
- data/lib/sequel_core/schema/schema_generator.rb +131 -0
- data/lib/sequel_core/schema/schema_sql.rb +131 -0
- data/lib/sequel_core/worker.rb +58 -0
- data/spec/adapters/informix_spec.rb +139 -0
- data/spec/adapters/mysql_spec.rb +330 -0
- data/spec/adapters/oracle_spec.rb +130 -0
- data/spec/adapters/postgres_spec.rb +189 -0
- data/spec/adapters/sqlite_spec.rb +345 -0
- data/spec/array_keys_spec.rb +679 -0
- data/spec/connection_pool_spec.rb +356 -0
- data/spec/core_ext_spec.rb +67 -0
- data/spec/core_sql_spec.rb +301 -0
- data/spec/database_spec.rb +812 -0
- data/spec/dataset_spec.rb +2381 -0
- data/spec/migration_spec.rb +261 -0
- data/spec/pretty_table_spec.rb +66 -0
- data/spec/rcov.opts +4 -0
- data/spec/schema_generator_spec.rb +86 -0
- data/spec/schema_spec.rb +230 -0
- data/spec/sequelizer_spec.rb +448 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/worker_spec.rb +96 -0
- metadata +162 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
if !Sequel.const_defined?('ODBC')
|
2
|
+
require File.join(File.dirname(__FILE__), 'odbc')
|
3
|
+
end
|
4
|
+
|
5
|
+
module Sequel
|
6
|
+
module ODBC
|
7
|
+
module MSSQL
|
8
|
+
class Database < ODBC::Database
|
9
|
+
set_adapter_scheme :odbc_mssql
|
10
|
+
|
11
|
+
def dataset(opts = nil)
|
12
|
+
MSSQL::Dataset.new(self, opts)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Dataset < ODBC::Dataset
|
17
|
+
# Allows you to do .nolock on a query
|
18
|
+
def nolock
|
19
|
+
clone_merge(:with => "(NOLOCK)")
|
20
|
+
end
|
21
|
+
|
22
|
+
# Formats a SELECT statement using the given options and the dataset
|
23
|
+
# options.
|
24
|
+
def select_sql(opts = nil)
|
25
|
+
opts = opts ? @opts.merge(opts) : @opts
|
26
|
+
|
27
|
+
if sql = opts[:sql]
|
28
|
+
return sql
|
29
|
+
end
|
30
|
+
|
31
|
+
# ADD TOP to SELECT string for LIMITS
|
32
|
+
if limit = opts[:limit]
|
33
|
+
top = "TOP #{limit} "
|
34
|
+
raise Error, "Offset not supported" if opts[:offset]
|
35
|
+
end
|
36
|
+
|
37
|
+
columns = opts[:select]
|
38
|
+
select_columns = columns ? column_list(columns) : WILDCARD
|
39
|
+
|
40
|
+
if distinct = opts[:distinct]
|
41
|
+
distinct_clause = distinct.empty? ? "DISTINCT" : "DISTINCT ON (#{column_list(distinct)})"
|
42
|
+
sql = "SELECT #{top}#{distinct_clause} #{select_columns}"
|
43
|
+
else
|
44
|
+
sql = "SELECT #{top}#{select_columns}"
|
45
|
+
end
|
46
|
+
|
47
|
+
if opts[:from]
|
48
|
+
sql << " FROM #{source_list(opts[:from])}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# ADD WITH to SELECT string for NOLOCK
|
52
|
+
if with = opts[:with]
|
53
|
+
sql << " WITH #{with}"
|
54
|
+
end
|
55
|
+
|
56
|
+
if join = opts[:join]
|
57
|
+
sql << join
|
58
|
+
end
|
59
|
+
|
60
|
+
if where = opts[:where]
|
61
|
+
sql << " WHERE #{where}"
|
62
|
+
end
|
63
|
+
|
64
|
+
if group = opts[:group]
|
65
|
+
sql << " GROUP BY #{column_list(group)}"
|
66
|
+
end
|
67
|
+
|
68
|
+
if order = opts[:order]
|
69
|
+
sql << " ORDER BY #{column_list(order)}"
|
70
|
+
end
|
71
|
+
|
72
|
+
if having = opts[:having]
|
73
|
+
sql << " HAVING #{having}"
|
74
|
+
end
|
75
|
+
|
76
|
+
if union = opts[:union]
|
77
|
+
sql << (opts[:union_all] ? \
|
78
|
+
" UNION ALL #{union.sql}" : " UNION #{union.sql}")
|
79
|
+
elsif intersect = opts[:intersect]
|
80
|
+
sql << (opts[:intersect_all] ? \
|
81
|
+
" INTERSECT ALL #{intersect.sql}" : " INTERSECT #{intersect.sql}")
|
82
|
+
elsif except = opts[:except]
|
83
|
+
sql << (opts[:except_all] ? \
|
84
|
+
" EXCEPT ALL #{except.sql}" : " EXCEPT #{except.sql}")
|
85
|
+
end
|
86
|
+
|
87
|
+
sql
|
88
|
+
end
|
89
|
+
alias_method :sql, :select_sql
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'openbase'
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module OpenBase
|
5
|
+
class Database < Sequel::Database
|
6
|
+
set_adapter_scheme :openbase
|
7
|
+
|
8
|
+
def connect
|
9
|
+
OpenBase.new(
|
10
|
+
opts[:database],
|
11
|
+
opts[:host] || 'localhost',
|
12
|
+
opts[:user],
|
13
|
+
opts[:password]
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def disconnect
|
18
|
+
# would this work?
|
19
|
+
@pool.disconnect {|c| c.disconnect}
|
20
|
+
end
|
21
|
+
|
22
|
+
def dataset(opts = nil)
|
23
|
+
OpenBase::Dataset.new(self, opts)
|
24
|
+
end
|
25
|
+
|
26
|
+
def execute(sql)
|
27
|
+
@logger.info(sql) if @logger
|
28
|
+
@pool.hold {|conn| conn.execute(sql)}
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :do, :execute
|
32
|
+
end
|
33
|
+
|
34
|
+
class Dataset < Sequel::Dataset
|
35
|
+
def literal(v)
|
36
|
+
case v
|
37
|
+
when Time
|
38
|
+
literal(v.iso8601)
|
39
|
+
else
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def fetch_rows(sql, &block)
|
45
|
+
@db.synchronize do
|
46
|
+
result = @db.execute sql
|
47
|
+
begin
|
48
|
+
@columns = result.column_infos.map {|c| c.name.to_sym}
|
49
|
+
result.each do |r|
|
50
|
+
row = {}
|
51
|
+
r.each_with_index {|v, i| row[@columns[i]] = v}
|
52
|
+
yield row
|
53
|
+
end
|
54
|
+
ensure
|
55
|
+
# result.close
|
56
|
+
end
|
57
|
+
end
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def array_tuples_fetch_rows(sql, &block)
|
62
|
+
@db.synchronize do
|
63
|
+
result = @db.execute sql
|
64
|
+
begin
|
65
|
+
@columns = result.column_infos.map {|c| c.name.to_sym}
|
66
|
+
result.each do |r|
|
67
|
+
r.keys = @columns
|
68
|
+
yield r
|
69
|
+
end
|
70
|
+
ensure
|
71
|
+
# cursor.close
|
72
|
+
end
|
73
|
+
end
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
def insert(*values)
|
78
|
+
@db.do insert_sql(*values)
|
79
|
+
end
|
80
|
+
|
81
|
+
def update(*args, &block)
|
82
|
+
@db.do update_sql(*args, &block)
|
83
|
+
end
|
84
|
+
|
85
|
+
def delete(opts = nil)
|
86
|
+
@db.do delete_sql(opts)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'oci8'
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Oracle
|
5
|
+
class Database < Sequel::Database
|
6
|
+
set_adapter_scheme :oracle
|
7
|
+
|
8
|
+
# AUTO_INCREMENT = 'IDENTITY(1,1)'.freeze
|
9
|
+
#
|
10
|
+
# def auto_increment_sql
|
11
|
+
# AUTO_INCREMENT
|
12
|
+
# end
|
13
|
+
|
14
|
+
def connect
|
15
|
+
if @opts[:database]
|
16
|
+
dbname = @opts[:host] ? \
|
17
|
+
"//#{@opts[:host]}/#{@opts[:database]}" : @opts[:database]
|
18
|
+
else
|
19
|
+
dbname = @opts[:host]
|
20
|
+
end
|
21
|
+
conn = OCI8.new(@opts[:user], @opts[:password], dbname, @opts[:privilege])
|
22
|
+
conn.autocommit = true
|
23
|
+
conn.non_blocking = true
|
24
|
+
conn
|
25
|
+
end
|
26
|
+
|
27
|
+
def disconnect
|
28
|
+
@pool.disconnect {|c| c.logoff}
|
29
|
+
end
|
30
|
+
|
31
|
+
def dataset(opts = nil)
|
32
|
+
Oracle::Dataset.new(self, opts)
|
33
|
+
end
|
34
|
+
|
35
|
+
def execute(sql)
|
36
|
+
@logger.info(sql) if @logger
|
37
|
+
@pool.hold {|conn| conn.exec(sql)}
|
38
|
+
end
|
39
|
+
|
40
|
+
alias_method :do, :execute
|
41
|
+
end
|
42
|
+
|
43
|
+
class Dataset < Sequel::Dataset
|
44
|
+
def literal(v)
|
45
|
+
case v
|
46
|
+
when Time
|
47
|
+
literal(v.iso8601)
|
48
|
+
else
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def fetch_rows(sql, &block)
|
54
|
+
@db.synchronize do
|
55
|
+
cursor = @db.execute sql
|
56
|
+
begin
|
57
|
+
@columns = cursor.get_col_names.map {|c| c.to_sym}
|
58
|
+
while r = cursor.fetch
|
59
|
+
row = {}
|
60
|
+
r.each_with_index {|v, i| row[@columns[i]] = v}
|
61
|
+
yield row
|
62
|
+
end
|
63
|
+
ensure
|
64
|
+
cursor.close
|
65
|
+
end
|
66
|
+
end
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def array_tuples_fetch_rows(sql, &block)
|
71
|
+
@db.synchronize do
|
72
|
+
cursor = @db.execute sql
|
73
|
+
begin
|
74
|
+
@columns = cursor.get_col_names.map {|c| c.to_sym}
|
75
|
+
while r = cursor.fetch
|
76
|
+
r.keys = columns
|
77
|
+
yield r
|
78
|
+
end
|
79
|
+
ensure
|
80
|
+
cursor.close
|
81
|
+
end
|
82
|
+
end
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
def insert(*values)
|
87
|
+
@db.do insert_sql(*values)
|
88
|
+
end
|
89
|
+
|
90
|
+
def update(*args, &block)
|
91
|
+
@db.do update_sql(*args, &block)
|
92
|
+
end
|
93
|
+
|
94
|
+
def delete(opts = nil)
|
95
|
+
@db.do delete_sql(opts)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,519 @@
|
|
1
|
+
require 'postgres'
|
2
|
+
|
3
|
+
class PGconn
|
4
|
+
# the pure-ruby postgres adapter does not have a quote method.
|
5
|
+
TRUE = 't'.freeze
|
6
|
+
FALSE = 'f'.freeze
|
7
|
+
NULL = 'NULL'.freeze
|
8
|
+
|
9
|
+
unless methods.include?('quote')
|
10
|
+
def self.quote(obj)
|
11
|
+
case obj
|
12
|
+
when true
|
13
|
+
TRUE
|
14
|
+
when false
|
15
|
+
FALSE
|
16
|
+
when nil
|
17
|
+
NULL
|
18
|
+
when String
|
19
|
+
"'#{obj}'"
|
20
|
+
else
|
21
|
+
obj.to_s
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
# The postgres gem's string quoting doesn't render string literals properly, which this fixes.
|
28
|
+
#
|
29
|
+
# "a basic string" #=> 'a basic string'
|
30
|
+
# "this\or that" #=> E'this\\or that'
|
31
|
+
#
|
32
|
+
# See <http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html> for details.
|
33
|
+
def quote_with_proper_escaping(s)
|
34
|
+
value = quote_without_proper_escaping(s)
|
35
|
+
value = "E#{value}" if value =~ /\\/
|
36
|
+
return value
|
37
|
+
end
|
38
|
+
alias_method :quote_without_proper_escaping, :quote
|
39
|
+
alias_method :quote, :quote_with_proper_escaping
|
40
|
+
end
|
41
|
+
|
42
|
+
def connected?
|
43
|
+
status == PGconn::CONNECTION_OK
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute(sql)
|
47
|
+
begin
|
48
|
+
async_exec(sql)
|
49
|
+
rescue PGError => e
|
50
|
+
unless connected?
|
51
|
+
reset
|
52
|
+
async_exec(sql)
|
53
|
+
else
|
54
|
+
raise e
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_accessor :transaction_in_progress
|
60
|
+
|
61
|
+
SELECT_CURRVAL = "SELECT currval('%s')".freeze
|
62
|
+
|
63
|
+
def last_insert_id(table)
|
64
|
+
@table_sequences ||= {}
|
65
|
+
if !@table_sequences.include?(table)
|
66
|
+
pkey_and_seq = pkey_and_sequence(table)
|
67
|
+
if pkey_and_seq
|
68
|
+
@table_sequences[table] = pkey_and_seq[1]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
if seq = @table_sequences[table]
|
72
|
+
r = async_query(SELECT_CURRVAL % seq)
|
73
|
+
return r[0][0].to_i unless r.nil? || r.empty?
|
74
|
+
end
|
75
|
+
nil # primary key sequence not found
|
76
|
+
end
|
77
|
+
|
78
|
+
# Shamelessly appropriated from ActiveRecord's Postgresql adapter.
|
79
|
+
|
80
|
+
SELECT_PK_AND_SERIAL_SEQUENCE = <<-end_sql
|
81
|
+
SELECT attr.attname, name.nspname, seq.relname
|
82
|
+
FROM pg_class seq, pg_attribute attr, pg_depend dep,
|
83
|
+
pg_namespace name, pg_constraint cons
|
84
|
+
WHERE seq.oid = dep.objid
|
85
|
+
AND seq.relnamespace = name.oid
|
86
|
+
AND seq.relkind = 'S'
|
87
|
+
AND attr.attrelid = dep.refobjid
|
88
|
+
AND attr.attnum = dep.refobjsubid
|
89
|
+
AND attr.attrelid = cons.conrelid
|
90
|
+
AND attr.attnum = cons.conkey[1]
|
91
|
+
AND cons.contype = 'p'
|
92
|
+
AND dep.refobjid = '%s'::regclass
|
93
|
+
end_sql
|
94
|
+
|
95
|
+
SELECT_PK_AND_CUSTOM_SEQUENCE = <<-end_sql
|
96
|
+
SELECT attr.attname, name.nspname, split_part(def.adsrc, '''', 2)
|
97
|
+
FROM pg_class t
|
98
|
+
JOIN pg_namespace name ON (t.relnamespace = name.oid)
|
99
|
+
JOIN pg_attribute attr ON (t.oid = attrelid)
|
100
|
+
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
|
101
|
+
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
102
|
+
WHERE t.oid = '%s'::regclass
|
103
|
+
AND cons.contype = 'p'
|
104
|
+
AND def.adsrc ~* 'nextval'
|
105
|
+
end_sql
|
106
|
+
|
107
|
+
SELECT_PK = <<-end_sql
|
108
|
+
SELECT pg_attribute.attname
|
109
|
+
FROM pg_class, pg_attribute, pg_index
|
110
|
+
WHERE pg_class.oid = pg_attribute.attrelid AND
|
111
|
+
pg_class.oid = pg_index.indrelid AND
|
112
|
+
pg_index.indkey[0] = pg_attribute.attnum AND
|
113
|
+
pg_index.indisprimary = 't' AND
|
114
|
+
pg_class.relname = '%s'
|
115
|
+
end_sql
|
116
|
+
|
117
|
+
def pkey_and_sequence(table)
|
118
|
+
r = async_query(SELECT_PK_AND_SERIAL_SEQUENCE % table)
|
119
|
+
return [r[0].first, r[0].last] unless r.nil? or r.empty?
|
120
|
+
|
121
|
+
r = async_query(SELECT_PK_AND_CUSTOM_SEQUENCE % table)
|
122
|
+
return [r[0].first, r[0].last] unless r.nil? or r.empty?
|
123
|
+
rescue
|
124
|
+
nil
|
125
|
+
end
|
126
|
+
|
127
|
+
def primary_key(table)
|
128
|
+
r = async_query(SELECT_PK % table)
|
129
|
+
pkey = r[0].first unless r.nil? or r.empty?
|
130
|
+
return pkey.to_sym if pkey
|
131
|
+
rescue
|
132
|
+
nil
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
class String
|
137
|
+
POSTGRES_BOOL_TRUE = 't'.freeze
|
138
|
+
POSTGRES_BOOL_FALSE = 'f'.freeze
|
139
|
+
|
140
|
+
def postgres_to_bool
|
141
|
+
if self == POSTGRES_BOOL_TRUE
|
142
|
+
true
|
143
|
+
elsif self == POSTGRES_BOOL_FALSE
|
144
|
+
false
|
145
|
+
else
|
146
|
+
nil
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
module Sequel
|
152
|
+
module Postgres
|
153
|
+
PG_TYPES = {
|
154
|
+
16 => :postgres_to_bool,
|
155
|
+
20 => :to_i,
|
156
|
+
21 => :to_i,
|
157
|
+
22 => :to_i,
|
158
|
+
23 => :to_i,
|
159
|
+
700 => :to_f,
|
160
|
+
701 => :to_f,
|
161
|
+
1114 => :to_time
|
162
|
+
}
|
163
|
+
|
164
|
+
if PGconn.respond_to?(:translate_results=)
|
165
|
+
PGconn.translate_results = true
|
166
|
+
AUTO_TRANSLATE = true
|
167
|
+
else
|
168
|
+
AUTO_TRANSLATE = false
|
169
|
+
end
|
170
|
+
|
171
|
+
class Database < Sequel::Database
|
172
|
+
set_adapter_scheme :postgres
|
173
|
+
|
174
|
+
def connect
|
175
|
+
conn = PGconn.connect(
|
176
|
+
@opts[:host] || 'localhost',
|
177
|
+
@opts[:port] || 5432,
|
178
|
+
'', '',
|
179
|
+
@opts[:database],
|
180
|
+
@opts[:user],
|
181
|
+
@opts[:password]
|
182
|
+
)
|
183
|
+
if encoding = @opts[:encoding] || @opts[:charset]
|
184
|
+
conn.set_client_encoding(encoding)
|
185
|
+
end
|
186
|
+
conn
|
187
|
+
end
|
188
|
+
|
189
|
+
def disconnect
|
190
|
+
@pool.disconnect {|c| c.close}
|
191
|
+
end
|
192
|
+
|
193
|
+
def dataset(opts = nil)
|
194
|
+
Postgres::Dataset.new(self, opts)
|
195
|
+
end
|
196
|
+
|
197
|
+
RELATION_QUERY = {:from => [:pg_class], :select => [:relname]}.freeze
|
198
|
+
RELATION_FILTER = "(relkind = 'r') AND (relname !~ '^pg|sql')".freeze
|
199
|
+
SYSTEM_TABLE_REGEXP = /^pg|sql/.freeze
|
200
|
+
|
201
|
+
def tables
|
202
|
+
dataset(RELATION_QUERY).filter(RELATION_FILTER).map {|r| r[:relname].to_sym}
|
203
|
+
end
|
204
|
+
|
205
|
+
def locks
|
206
|
+
dataset.from("pg_class, pg_locks").
|
207
|
+
select("pg_class.relname, pg_locks.*").
|
208
|
+
filter("pg_class.relfilenode=pg_locks.relation")
|
209
|
+
end
|
210
|
+
|
211
|
+
def execute(sql)
|
212
|
+
@logger.info(sql) if @logger
|
213
|
+
@pool.hold {|conn| conn.execute(sql)}
|
214
|
+
rescue => e
|
215
|
+
@logger.error(e.message) if @logger
|
216
|
+
raise e
|
217
|
+
end
|
218
|
+
|
219
|
+
def execute_and_forget(sql)
|
220
|
+
@logger.info(sql) if @logger
|
221
|
+
@pool.hold {|conn| conn.execute(sql).clear}
|
222
|
+
rescue => e
|
223
|
+
@logger.error(e.message) if @logger
|
224
|
+
raise e
|
225
|
+
end
|
226
|
+
|
227
|
+
def primary_key_for_table(conn, table)
|
228
|
+
@primary_keys ||= {}
|
229
|
+
@primary_keys[table] ||= conn.primary_key(table)
|
230
|
+
end
|
231
|
+
|
232
|
+
RE_CURRVAL_ERROR = /currval of sequence "(.*)" is not yet defined in this session/.freeze
|
233
|
+
|
234
|
+
def insert_result(conn, table, values)
|
235
|
+
begin
|
236
|
+
result = conn.last_insert_id(table)
|
237
|
+
return result if result
|
238
|
+
rescue PGError => e
|
239
|
+
# An error could occur if the inserted values include a primary key
|
240
|
+
# value, while the primary key is serial.
|
241
|
+
if e.message =~ RE_CURRVAL_ERROR
|
242
|
+
raise Error, "Could not return primary key value for the inserted record. Are you specifying a primary key value for a serial primary key?"
|
243
|
+
else
|
244
|
+
raise e
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
case values
|
249
|
+
when Hash
|
250
|
+
values[primary_key_for_table(conn, table)]
|
251
|
+
when Array
|
252
|
+
values.first
|
253
|
+
else
|
254
|
+
nil
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def execute_insert(sql, table, values)
|
259
|
+
@logger.info(sql) if @logger
|
260
|
+
@pool.hold do |conn|
|
261
|
+
conn.execute(sql).clear
|
262
|
+
insert_result(conn, table, values)
|
263
|
+
end
|
264
|
+
rescue => e
|
265
|
+
@logger.error(e.message) if @logger
|
266
|
+
raise e
|
267
|
+
end
|
268
|
+
|
269
|
+
def synchronize(&block)
|
270
|
+
@pool.hold(&block)
|
271
|
+
end
|
272
|
+
|
273
|
+
SQL_BEGIN = 'BEGIN'.freeze
|
274
|
+
SQL_COMMIT = 'COMMIT'.freeze
|
275
|
+
SQL_ROLLBACK = 'ROLLBACK'.freeze
|
276
|
+
|
277
|
+
def transaction
|
278
|
+
@pool.hold do |conn|
|
279
|
+
if conn.transaction_in_progress
|
280
|
+
yield conn
|
281
|
+
else
|
282
|
+
@logger.info(SQL_BEGIN) if @logger
|
283
|
+
conn.async_exec(SQL_BEGIN)
|
284
|
+
begin
|
285
|
+
conn.transaction_in_progress = true
|
286
|
+
result = yield
|
287
|
+
begin
|
288
|
+
@logger.info(SQL_COMMIT) if @logger
|
289
|
+
conn.async_exec(SQL_COMMIT)
|
290
|
+
rescue => e
|
291
|
+
@logger.error(e.message) if @logger
|
292
|
+
raise e
|
293
|
+
end
|
294
|
+
result
|
295
|
+
rescue => e
|
296
|
+
@logger.info(SQL_ROLLBACK) if @logger
|
297
|
+
conn.async_exec(SQL_ROLLBACK) rescue nil
|
298
|
+
raise e unless Error::Rollback === e
|
299
|
+
ensure
|
300
|
+
conn.transaction_in_progress = nil
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def serial_primary_key_options
|
307
|
+
{:primary_key => true, :type => :serial}
|
308
|
+
end
|
309
|
+
|
310
|
+
def drop_table_sql(name)
|
311
|
+
"DROP TABLE #{name} CASCADE"
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
class Dataset < Sequel::Dataset
|
316
|
+
def literal(v)
|
317
|
+
case v
|
318
|
+
when LiteralString
|
319
|
+
v
|
320
|
+
when String, Fixnum, Float, TrueClass, FalseClass
|
321
|
+
PGconn.quote(v)
|
322
|
+
else
|
323
|
+
super
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
def match_expr(l, r)
|
328
|
+
case r
|
329
|
+
when Regexp
|
330
|
+
r.casefold? ? \
|
331
|
+
"(#{literal(l)} ~* #{literal(r.source)})" :
|
332
|
+
"(#{literal(l)} ~ #{literal(r.source)})"
|
333
|
+
else
|
334
|
+
super
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
FOR_UPDATE = ' FOR UPDATE'.freeze
|
339
|
+
FOR_SHARE = ' FOR SHARE'.freeze
|
340
|
+
|
341
|
+
def select_sql(opts = nil)
|
342
|
+
row_lock_mode = opts ? opts[:lock] : @opts[:lock]
|
343
|
+
sql = super
|
344
|
+
case row_lock_mode
|
345
|
+
when :update
|
346
|
+
sql << FOR_UPDATE
|
347
|
+
when :share
|
348
|
+
sql << FOR_SHARE
|
349
|
+
end
|
350
|
+
sql
|
351
|
+
end
|
352
|
+
|
353
|
+
def for_update
|
354
|
+
clone_merge(:lock => :update)
|
355
|
+
end
|
356
|
+
|
357
|
+
def for_share
|
358
|
+
clone_merge(:lock => :share)
|
359
|
+
end
|
360
|
+
|
361
|
+
EXPLAIN = 'EXPLAIN '.freeze
|
362
|
+
EXPLAIN_ANALYZE = 'EXPLAIN ANALYZE '.freeze
|
363
|
+
QUERY_PLAN = 'QUERY PLAN'.to_sym
|
364
|
+
|
365
|
+
def explain(opts = nil)
|
366
|
+
analysis = []
|
367
|
+
fetch_rows(EXPLAIN + select_sql(opts)) do |r|
|
368
|
+
analysis << r[QUERY_PLAN]
|
369
|
+
end
|
370
|
+
analysis.join("\r\n")
|
371
|
+
end
|
372
|
+
|
373
|
+
def analyze(opts = nil)
|
374
|
+
analysis = []
|
375
|
+
fetch_rows(EXPLAIN_ANALYZE + select_sql(opts)) do |r|
|
376
|
+
analysis << r[QUERY_PLAN]
|
377
|
+
end
|
378
|
+
analysis.join("\r\n")
|
379
|
+
end
|
380
|
+
|
381
|
+
LOCK = 'LOCK TABLE %s IN %s MODE'.freeze
|
382
|
+
|
383
|
+
ACCESS_SHARE = 'ACCESS SHARE'.freeze
|
384
|
+
ROW_SHARE = 'ROW SHARE'.freeze
|
385
|
+
ROW_EXCLUSIVE = 'ROW EXCLUSIVE'.freeze
|
386
|
+
SHARE_UPDATE_EXCLUSIVE = 'SHARE UPDATE EXCLUSIVE'.freeze
|
387
|
+
SHARE = 'SHARE'.freeze
|
388
|
+
SHARE_ROW_EXCLUSIVE = 'SHARE ROW EXCLUSIVE'.freeze
|
389
|
+
EXCLUSIVE = 'EXCLUSIVE'.freeze
|
390
|
+
ACCESS_EXCLUSIVE = 'ACCESS EXCLUSIVE'.freeze
|
391
|
+
|
392
|
+
# Locks the table with the specified mode.
|
393
|
+
def lock(mode, &block)
|
394
|
+
sql = LOCK % [@opts[:from], mode]
|
395
|
+
@db.synchronize do
|
396
|
+
if block # perform locking inside a transaction and yield to block
|
397
|
+
@db.transaction {@db.execute_and_forget(sql); yield}
|
398
|
+
else
|
399
|
+
@db.execute_and_forget(sql) # lock without a transaction
|
400
|
+
self
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
def insert(*values)
|
406
|
+
@db.execute_insert(insert_sql(*values), @opts[:from],
|
407
|
+
values.size == 1 ? values.first : values)
|
408
|
+
end
|
409
|
+
|
410
|
+
def update(*args, &block)
|
411
|
+
@db.synchronize do
|
412
|
+
result = @db.execute(update_sql(*args, &block))
|
413
|
+
begin
|
414
|
+
affected = result.cmdtuples
|
415
|
+
ensure
|
416
|
+
result.clear
|
417
|
+
end
|
418
|
+
affected
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
def delete(opts = nil)
|
423
|
+
@db.synchronize do
|
424
|
+
result = @db.execute(delete_sql(opts))
|
425
|
+
begin
|
426
|
+
affected = result.cmdtuples
|
427
|
+
ensure
|
428
|
+
result.clear
|
429
|
+
end
|
430
|
+
affected
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
def fetch_rows(sql, &block)
|
435
|
+
@db.synchronize do
|
436
|
+
result = @db.execute(sql)
|
437
|
+
begin
|
438
|
+
conv = row_converter(result)
|
439
|
+
result.each {|r| yield conv[r]}
|
440
|
+
ensure
|
441
|
+
result.clear
|
442
|
+
end
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
@@converters_mutex = Mutex.new
|
447
|
+
@@converters = {}
|
448
|
+
|
449
|
+
def row_converter(result)
|
450
|
+
@columns = []; translators = []
|
451
|
+
result.fields.each_with_index do |f, idx|
|
452
|
+
@columns << f.to_sym
|
453
|
+
translators << PG_TYPES[result.type(idx)]
|
454
|
+
end
|
455
|
+
|
456
|
+
# create result signature and memoize the converter
|
457
|
+
sig = [@columns, translators].hash
|
458
|
+
@@converters_mutex.synchronize do
|
459
|
+
@@converters[sig] ||= compile_converter(@columns, translators)
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
def compile_converter(columns, translators)
|
464
|
+
used_columns = []
|
465
|
+
kvs = []
|
466
|
+
columns.each_with_index do |column, idx|
|
467
|
+
next if used_columns.include?(column)
|
468
|
+
used_columns << column
|
469
|
+
|
470
|
+
if !AUTO_TRANSLATE and translator = translators[idx]
|
471
|
+
kvs << ":\"#{column}\" => ((t = r[#{idx}]) ? t.#{translator} : nil)"
|
472
|
+
else
|
473
|
+
kvs << ":\"#{column}\" => r[#{idx}]"
|
474
|
+
end
|
475
|
+
end
|
476
|
+
eval("lambda {|r| {#{kvs.join(COMMA_SEPARATOR)}}}")
|
477
|
+
end
|
478
|
+
|
479
|
+
def array_tuples_fetch_rows(sql, &block)
|
480
|
+
@db.synchronize do
|
481
|
+
result = @db.execute(sql)
|
482
|
+
begin
|
483
|
+
conv = array_tuples_row_converter(result)
|
484
|
+
result.each {|r| yield conv[r]}
|
485
|
+
ensure
|
486
|
+
result.clear
|
487
|
+
end
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
@@array_tuples_converters_mutex = Mutex.new
|
492
|
+
@@array_tuples_converters = {}
|
493
|
+
|
494
|
+
def array_tuples_row_converter(result)
|
495
|
+
@columns = []; translators = []
|
496
|
+
result.fields.each_with_index do |f, idx|
|
497
|
+
@columns << f.to_sym
|
498
|
+
translators << PG_TYPES[result.type(idx)]
|
499
|
+
end
|
500
|
+
|
501
|
+
# create result signature and memoize the converter
|
502
|
+
sig = [@columns, translators].hash
|
503
|
+
@@array_tuples_converters_mutex.synchronize do
|
504
|
+
@@array_tuples_converters[sig] ||= array_tuples_compile_converter(@columns, translators)
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
def array_tuples_compile_converter(columns, translators)
|
509
|
+
tr = []
|
510
|
+
columns.each_with_index do |column, idx|
|
511
|
+
if !AUTO_TRANSLATE and t = translators[idx]
|
512
|
+
tr << "if (v = r[#{idx}]); r[#{idx}] = v.#{t}; end"
|
513
|
+
end
|
514
|
+
end
|
515
|
+
eval("lambda {|r| r.keys = columns; #{tr.join(';')}; r}")
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|