sequel 2.9.0 → 2.10.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 +56 -0
- data/{README → README.rdoc} +85 -57
- data/Rakefile +10 -5
- data/bin/sequel +7 -16
- data/doc/advanced_associations.rdoc +5 -17
- data/doc/cheat_sheet.rdoc +18 -20
- data/doc/dataset_filtering.rdoc +8 -32
- data/doc/schema.rdoc +20 -0
- data/lib/sequel_core.rb +35 -1
- data/lib/sequel_core/adapters/ado.rb +1 -1
- data/lib/sequel_core/adapters/db2.rb +2 -2
- data/lib/sequel_core/adapters/dbi.rb +2 -11
- data/lib/sequel_core/adapters/do.rb +205 -0
- data/lib/sequel_core/adapters/do/mysql.rb +38 -0
- data/lib/sequel_core/adapters/do/postgres.rb +92 -0
- data/lib/sequel_core/adapters/do/sqlite.rb +31 -0
- data/lib/sequel_core/adapters/firebird.rb +298 -0
- data/lib/sequel_core/adapters/informix.rb +10 -1
- data/lib/sequel_core/adapters/jdbc.rb +78 -19
- data/lib/sequel_core/adapters/jdbc/h2.rb +69 -0
- data/lib/sequel_core/adapters/jdbc/mysql.rb +10 -0
- data/lib/sequel_core/adapters/jdbc/postgresql.rb +7 -3
- data/lib/sequel_core/adapters/mysql.rb +53 -77
- data/lib/sequel_core/adapters/odbc.rb +1 -1
- data/lib/sequel_core/adapters/openbase.rb +1 -1
- data/lib/sequel_core/adapters/oracle.rb +2 -2
- data/lib/sequel_core/adapters/postgres.rb +16 -14
- data/lib/sequel_core/adapters/shared/mysql.rb +44 -9
- data/lib/sequel_core/adapters/shared/oracle.rb +4 -5
- data/lib/sequel_core/adapters/shared/postgres.rb +86 -82
- data/lib/sequel_core/adapters/shared/sqlite.rb +39 -24
- data/lib/sequel_core/adapters/sqlite.rb +11 -1
- data/lib/sequel_core/connection_pool.rb +10 -2
- data/lib/sequel_core/core_sql.rb +13 -3
- data/lib/sequel_core/database.rb +131 -30
- data/lib/sequel_core/database/schema.rb +5 -5
- data/lib/sequel_core/dataset.rb +31 -6
- data/lib/sequel_core/dataset/convenience.rb +11 -11
- data/lib/sequel_core/dataset/query.rb +2 -2
- data/lib/sequel_core/dataset/sql.rb +6 -6
- data/lib/sequel_core/exceptions.rb +4 -0
- data/lib/sequel_core/migration.rb +4 -4
- data/lib/sequel_core/schema/generator.rb +19 -3
- data/lib/sequel_core/schema/sql.rb +24 -20
- data/lib/sequel_core/sql.rb +13 -16
- data/lib/sequel_core/version.rb +11 -0
- data/lib/sequel_model.rb +2 -0
- data/lib/sequel_model/base.rb +2 -2
- data/lib/sequel_model/hooks.rb +46 -7
- data/lib/sequel_model/record.rb +11 -9
- data/lib/sequel_model/schema.rb +1 -1
- data/lib/sequel_model/validations.rb +72 -61
- data/spec/adapters/firebird_spec.rb +371 -0
- data/spec/adapters/mysql_spec.rb +118 -62
- data/spec/adapters/oracle_spec.rb +5 -5
- data/spec/adapters/postgres_spec.rb +33 -18
- data/spec/adapters/sqlite_spec.rb +2 -2
- data/spec/integration/dataset_test.rb +3 -3
- data/spec/integration/schema_test.rb +55 -5
- data/spec/integration/spec_helper.rb +11 -0
- data/spec/integration/type_test.rb +59 -16
- data/spec/sequel_core/connection_pool_spec.rb +14 -0
- data/spec/sequel_core/core_sql_spec.rb +24 -14
- data/spec/sequel_core/database_spec.rb +96 -11
- data/spec/sequel_core/dataset_spec.rb +97 -37
- data/spec/sequel_core/expression_filters_spec.rb +51 -40
- data/spec/sequel_core/object_graph_spec.rb +2 -2
- data/spec/sequel_core/schema_generator_spec.rb +31 -6
- data/spec/sequel_core/schema_spec.rb +25 -9
- data/spec/sequel_core/spec_helper.rb +4 -1
- data/spec/sequel_core/version_spec.rb +7 -0
- data/spec/sequel_model/associations_spec.rb +1 -1
- data/spec/sequel_model/hooks_spec.rb +68 -13
- data/spec/sequel_model/model_spec.rb +4 -4
- data/spec/sequel_model/record_spec.rb +22 -0
- data/spec/sequel_model/spec_helper.rb +2 -1
- data/spec/sequel_model/validations_spec.rb +107 -7
- metadata +15 -5
@@ -51,7 +51,7 @@ module Sequel
|
|
51
51
|
def fetch_rows(sql)
|
52
52
|
execute(sql) do |result|
|
53
53
|
begin
|
54
|
-
@columns = result.column_infos.map
|
54
|
+
@columns = result.column_infos.map{|c| output_identifier(c.name)}
|
55
55
|
result.each do |r|
|
56
56
|
row = {}
|
57
57
|
r.each_with_index {|v, i| row[@columns[i]] = v}
|
@@ -90,10 +90,10 @@ module Sequel
|
|
90
90
|
def fetch_rows(sql, &block)
|
91
91
|
execute(sql) do |cursor|
|
92
92
|
begin
|
93
|
-
@columns = cursor.get_col_names.map
|
93
|
+
@columns = cursor.get_col_names.map{|c| output_identifier(c)}
|
94
94
|
while r = cursor.fetch
|
95
95
|
row = {}
|
96
|
-
r.each_with_index {|v, i| row[columns[i]] = v unless columns[i] == :raw_rnum_}
|
96
|
+
r.each_with_index {|v, i| row[@columns[i]] = v unless @columns[i] == :raw_rnum_}
|
97
97
|
yield row
|
98
98
|
end
|
99
99
|
ensure
|
@@ -15,7 +15,7 @@ rescue LoadError => e
|
|
15
15
|
# If there is no escape_string instead method, but there is an
|
16
16
|
# escape class method, use that instead.
|
17
17
|
def escape_string(str)
|
18
|
-
self.class.escape(str)
|
18
|
+
Sequel::Postgres.force_standard_strings ? str.gsub("'", "''") : self.class.escape(str)
|
19
19
|
end
|
20
20
|
else
|
21
21
|
# Raise an error if no valid string escaping method can be found.
|
@@ -103,19 +103,12 @@ module Sequel
|
|
103
103
|
}
|
104
104
|
|
105
105
|
@use_iso_date_format = true
|
106
|
-
@client_min_messages = :warning
|
107
106
|
|
108
107
|
# As an optimization, Sequel sets the date style to ISO, so that PostgreSQL provides
|
109
108
|
# the date in a known format that Sequel can parse faster. This can be turned off
|
110
109
|
# if you require a date style other than ISO.
|
111
110
|
metaattr_accessor :use_iso_date_format
|
112
111
|
|
113
|
-
# By default, Sequel sets the minimum level of log messages sent to the client
|
114
|
-
# to WARNING, where PostgreSQL uses a default of NOTICE. This is to avoid a lot
|
115
|
-
# of mostly useless messages when running migrations, such as a couple of lines
|
116
|
-
# for every serial primary key field.
|
117
|
-
metaattr_accessor :client_min_messages
|
118
|
-
|
119
112
|
# PGconn subclass for connection specific methods used with the
|
120
113
|
# pg, postgres, or postgres-pr driver.
|
121
114
|
class Adapter < ::PGconn
|
@@ -132,11 +125,6 @@ module Sequel
|
|
132
125
|
@db.log_info(sql)
|
133
126
|
execute(sql)
|
134
127
|
end
|
135
|
-
if cmm = Postgres.client_min_messages
|
136
|
-
sql = "SET client_min_messages = '#{cmm.to_s.upcase}'"
|
137
|
-
@db.log_info(sql)
|
138
|
-
execute(sql)
|
139
|
-
end
|
140
128
|
end
|
141
129
|
|
142
130
|
# Execute the given SQL with this connection. If a block is given,
|
@@ -324,7 +312,7 @@ module Sequel
|
|
324
312
|
cols = []
|
325
313
|
execute(sql) do |res|
|
326
314
|
res.nfields.times do |fieldnum|
|
327
|
-
cols << [fieldnum, PG_TYPES[res.ftype(fieldnum)], res.fname(fieldnum)
|
315
|
+
cols << [fieldnum, PG_TYPES[res.ftype(fieldnum)], output_identifier(res.fname(fieldnum))]
|
328
316
|
end
|
329
317
|
@columns = cols.map{|c| c.at(2)}
|
330
318
|
res.ntuples.times do |recnum|
|
@@ -337,6 +325,20 @@ module Sequel
|
|
337
325
|
end
|
338
326
|
end
|
339
327
|
end
|
328
|
+
|
329
|
+
# Literalize strings and blobs using code from the native adapter.
|
330
|
+
def literal(v)
|
331
|
+
case v
|
332
|
+
when LiteralString
|
333
|
+
v
|
334
|
+
when SQL::Blob
|
335
|
+
db.synchronize{|c| "'#{c.escape_bytea(v)}'"}
|
336
|
+
when String
|
337
|
+
db.synchronize{|c| "'#{c.escape_string(v)}'"}
|
338
|
+
else
|
339
|
+
super
|
340
|
+
end
|
341
|
+
end
|
340
342
|
|
341
343
|
if SEQUEL_POSTGRES_USES_PG
|
342
344
|
|
@@ -6,6 +6,9 @@ module Sequel
|
|
6
6
|
end
|
7
7
|
end
|
8
8
|
module MySQL
|
9
|
+
# Set the default options used for CREATE TABLE
|
10
|
+
metaattr_accessor :default_charset, :default_collate, :default_engine
|
11
|
+
|
9
12
|
# Methods shared by Database instances that connect to MySQL,
|
10
13
|
# currently supported by the native and JDBC adapters.
|
11
14
|
module DatabaseMethods
|
@@ -13,7 +16,8 @@ module Sequel
|
|
13
16
|
NOT_NULL = Sequel::Schema::SQL::NOT_NULL
|
14
17
|
NULL = Sequel::Schema::SQL::NULL
|
15
18
|
PRIMARY_KEY = Sequel::Schema::SQL::PRIMARY_KEY
|
16
|
-
TYPES = Sequel::Schema::SQL::TYPES
|
19
|
+
TYPES = Sequel::Schema::SQL::TYPES.merge(DateTime=>'datetime', \
|
20
|
+
TrueClass=>'tinyint', FalseClass=>'tinyint')
|
17
21
|
UNIQUE = Sequel::Schema::SQL::UNIQUE
|
18
22
|
UNSIGNED = Sequel::Schema::SQL::UNSIGNED
|
19
23
|
|
@@ -50,6 +54,16 @@ module Sequel
|
|
50
54
|
"#{", FOREIGN KEY (#{quote_identifier(column[:name])})" unless column[:type] == :check}#{super(column)}"
|
51
55
|
end
|
52
56
|
|
57
|
+
# Use MySQL specific syntax for engine type and character encoding
|
58
|
+
def create_table_sql_list(name, columns, indexes = nil, options = {})
|
59
|
+
options[:engine] = Sequel::MySQL.default_engine unless options.include?(:engine)
|
60
|
+
options[:charset] = Sequel::MySQL.default_charset unless options.include?(:charset)
|
61
|
+
options[:collate] = Sequel::MySQL.default_collate unless options.include?(:collate)
|
62
|
+
sql = ["CREATE TABLE #{quote_schema_table(name)} (#{column_list_sql(columns)})#{" ENGINE=#{options[:engine]}" if options[:engine]}#{" DEFAULT CHARSET=#{options[:charset]}" if options[:charset]}#{" DEFAULT COLLATE=#{options[:collate]}" if options[:collate]}"]
|
63
|
+
sql.concat(index_list_sql_list(name, indexes)) if indexes && !indexes.empty?
|
64
|
+
sql
|
65
|
+
end
|
66
|
+
|
53
67
|
# Handle MySQL specific index SQL syntax
|
54
68
|
def index_definition_sql(table_name, index)
|
55
69
|
index_name = quote_identifier(index[:name] || default_index_name(table_name, index[:columns]))
|
@@ -67,13 +81,19 @@ module Sequel
|
|
67
81
|
|
68
82
|
# Get version of MySQL server, used for determined capabilities.
|
69
83
|
def server_version
|
70
|
-
m = /(\d+)\.(\d+)\.(\d+)/.match(get(:version
|
84
|
+
m = /(\d+)\.(\d+)\.(\d+)/.match(get(SQL::Function.new(:version)))
|
71
85
|
@server_version ||= (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
|
72
86
|
end
|
73
87
|
|
74
88
|
# Return an array of symbols specifying table names in the current database.
|
75
|
-
|
76
|
-
|
89
|
+
#
|
90
|
+
# Options:
|
91
|
+
# * :server - Set the server to use
|
92
|
+
def tables(opts={})
|
93
|
+
ds = self['SHOW TABLES'].server(opts[:server])
|
94
|
+
ds.identifier_output_method = nil
|
95
|
+
ds2 = dataset
|
96
|
+
ds.map{|r| ds2.send(:output_identifier, r.values.first)}
|
77
97
|
end
|
78
98
|
|
79
99
|
# Changes the database in use by issuing a USE statement. I would be
|
@@ -87,14 +107,22 @@ module Sequel
|
|
87
107
|
|
88
108
|
private
|
89
109
|
|
90
|
-
# MySQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers
|
91
|
-
def
|
92
|
-
|
110
|
+
# MySQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on input.
|
111
|
+
def identifier_input_method_default
|
112
|
+
nil
|
113
|
+
end
|
114
|
+
|
115
|
+
# MySQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on output.
|
116
|
+
def identifier_output_method_default
|
117
|
+
nil
|
93
118
|
end
|
94
119
|
|
95
120
|
# Use the MySQL specific DESCRIBE syntax to get a table description.
|
96
121
|
def schema_parse_table(table_name, opts)
|
97
|
-
self["DESCRIBE ?", SQL::Identifier.new(table_name)]
|
122
|
+
ds = self["DESCRIBE ?", SQL::Identifier.new(table_name)]
|
123
|
+
ds.identifier_output_method = nil
|
124
|
+
ds2 = dataset
|
125
|
+
ds.map do |row|
|
98
126
|
row.delete(:Extra)
|
99
127
|
row[:allow_null] = row.delete(:Null) == 'YES'
|
100
128
|
row[:default] = row.delete(:Default)
|
@@ -102,9 +130,14 @@ module Sequel
|
|
102
130
|
row[:default] = nil if row[:default].blank?
|
103
131
|
row[:db_type] = row.delete(:Type)
|
104
132
|
row[:type] = schema_column_type(row[:db_type])
|
105
|
-
[row.delete(:Field)
|
133
|
+
[ds2.send(:output_identifier, row.delete(:Field)), row]
|
106
134
|
end
|
107
135
|
end
|
136
|
+
|
137
|
+
# Override the standard type conversions with MySQL specific ones
|
138
|
+
def type_literal_base(column)
|
139
|
+
TYPES[column[:type]]
|
140
|
+
end
|
108
141
|
end
|
109
142
|
|
110
143
|
# Dataset methods shared by datasets that use MySQL databases.
|
@@ -198,6 +231,8 @@ module Sequel
|
|
198
231
|
BOOL_TRUE
|
199
232
|
when false
|
200
233
|
BOOL_FALSE
|
234
|
+
when DateTime, Time
|
235
|
+
v.strftime("'%Y-%m-%d %H:%M:%S'")
|
201
236
|
else
|
202
237
|
super
|
203
238
|
end
|
@@ -1,14 +1,13 @@
|
|
1
1
|
module Sequel
|
2
2
|
module Oracle
|
3
3
|
module DatabaseMethods
|
4
|
-
def tables
|
5
|
-
from(:tab).select(:tname).filter(:tabtype => 'TABLE')
|
6
|
-
|
7
|
-
end
|
4
|
+
def tables(opts={})
|
5
|
+
ds = from(:tab).server(opts[:server]).select(:tname).filter(:tabtype => 'TABLE')
|
6
|
+
ds.map{|r| ds.send(:output_identifier, r[:tname])}
|
8
7
|
end
|
9
8
|
|
10
9
|
def table_exists?(name)
|
11
|
-
from(:tab).filter(:tname => name
|
10
|
+
from(:tab).filter(:tname =>dataset.send(:input_identifier, name), :tabtype => 'TABLE').count > 0
|
12
11
|
end
|
13
12
|
end
|
14
13
|
|
@@ -32,7 +32,14 @@ module Sequel
|
|
32
32
|
# uses NativeExceptions, the native adapter uses PGError.
|
33
33
|
CONVERTED_EXCEPTIONS = []
|
34
34
|
|
35
|
+
@client_min_messages = :warning
|
35
36
|
@force_standard_strings = true
|
37
|
+
|
38
|
+
# By default, Sequel sets the minimum level of log messages sent to the client
|
39
|
+
# to WARNING, where PostgreSQL uses a default of NOTICE. This is to avoid a lot
|
40
|
+
# of mostly useless messages when running migrations, such as a couple of lines
|
41
|
+
# for every serial primary key field.
|
42
|
+
metaattr_accessor :client_min_messages
|
36
43
|
|
37
44
|
# By default, Sequel forces the use of standard strings, so that
|
38
45
|
# '\\' is interpreted as \\ and not \. While PostgreSQL defaults
|
@@ -46,7 +53,7 @@ module Sequel
|
|
46
53
|
attr_writer :db
|
47
54
|
|
48
55
|
SELECT_CURRVAL = "SELECT currval('%s')".freeze
|
49
|
-
SELECT_CUSTOM_SEQUENCE = <<-end_sql
|
56
|
+
SELECT_CUSTOM_SEQUENCE = proc do |schema, table| <<-end_sql
|
50
57
|
SELECT '"' || name.nspname || '"."' || CASE
|
51
58
|
WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
|
52
59
|
substr(split_part(def.adsrc, '''', 2),
|
@@ -60,10 +67,11 @@ module Sequel
|
|
60
67
|
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
61
68
|
WHERE cons.contype = 'p'
|
62
69
|
AND def.adsrc ~* 'nextval'
|
63
|
-
AND name.nspname = '
|
64
|
-
AND t.relname = '
|
70
|
+
#{"AND name.nspname = '#{schema}'" if schema}
|
71
|
+
AND t.relname = '#{table}'
|
65
72
|
end_sql
|
66
|
-
|
73
|
+
end
|
74
|
+
SELECT_PK = proc do |schema, table| <<-end_sql
|
67
75
|
SELECT pg_attribute.attname
|
68
76
|
FROM pg_class, pg_attribute, pg_index, pg_namespace
|
69
77
|
WHERE pg_class.oid = pg_attribute.attrelid
|
@@ -71,10 +79,11 @@ module Sequel
|
|
71
79
|
AND pg_class.oid = pg_index.indrelid
|
72
80
|
AND pg_index.indkey[0] = pg_attribute.attnum
|
73
81
|
AND pg_index.indisprimary = 't'
|
74
|
-
AND pg_namespace.nspname = '
|
75
|
-
AND pg_class.relname = '
|
82
|
+
#{"AND pg_namespace.nspname = '#{schema}'" if schema}
|
83
|
+
AND pg_class.relname = '#{table}'
|
76
84
|
end_sql
|
77
|
-
|
85
|
+
end
|
86
|
+
SELECT_SERIAL_SEQUENCE = proc do |schema, table| <<-end_sql
|
78
87
|
SELECT '"' || name.nspname || '"."' || seq.relname || '"'
|
79
88
|
FROM pg_class seq, pg_attribute attr, pg_depend dep,
|
80
89
|
pg_namespace name, pg_constraint cons
|
@@ -86,9 +95,10 @@ module Sequel
|
|
86
95
|
AND attr.attrelid = cons.conrelid
|
87
96
|
AND attr.attnum = cons.conkey[1]
|
88
97
|
AND cons.contype = 'p'
|
89
|
-
AND name.nspname = '
|
90
|
-
AND seq.relname = '
|
98
|
+
#{"AND name.nspname = '#{schema}'" if schema}
|
99
|
+
AND seq.relname = '#{table}'
|
91
100
|
end_sql
|
101
|
+
end
|
92
102
|
|
93
103
|
# Depth of the current transaction on this connection, used
|
94
104
|
# to implement multi-level transactions with savepoints.
|
@@ -106,6 +116,11 @@ module Sequel
|
|
106
116
|
# try it unconditionally and rescue any errors.
|
107
117
|
execute(sql) rescue nil
|
108
118
|
end
|
119
|
+
if cmm = Postgres.client_min_messages
|
120
|
+
sql = "SET client_min_messages = '#{cmm.to_s.upcase}'"
|
121
|
+
@db.log_info(sql)
|
122
|
+
execute(sql)
|
123
|
+
end
|
109
124
|
end
|
110
125
|
|
111
126
|
# Get the last inserted value for the given sequence.
|
@@ -120,7 +135,7 @@ module Sequel
|
|
120
135
|
|
121
136
|
# Get the primary key for the given table.
|
122
137
|
def primary_key(schema, table)
|
123
|
-
sql = SELECT_PK
|
138
|
+
sql = SELECT_PK[schema, table]
|
124
139
|
@db.log_info(sql)
|
125
140
|
execute(sql) do |r|
|
126
141
|
return single_value(r)
|
@@ -129,14 +144,14 @@ module Sequel
|
|
129
144
|
|
130
145
|
# Get the primary key and sequence for the given table.
|
131
146
|
def sequence(schema, table)
|
132
|
-
sql = SELECT_SERIAL_SEQUENCE
|
147
|
+
sql = SELECT_SERIAL_SEQUENCE[schema, table]
|
133
148
|
@db.log_info(sql)
|
134
149
|
execute(sql) do |r|
|
135
150
|
seq = single_value(r)
|
136
151
|
return seq if seq
|
137
152
|
end
|
138
153
|
|
139
|
-
sql = SELECT_CUSTOM_SEQUENCE
|
154
|
+
sql = SELECT_CUSTOM_SEQUENCE[schema, table]
|
140
155
|
@db.log_info(sql)
|
141
156
|
execute(sql) do |r|
|
142
157
|
return single_value(r)
|
@@ -155,6 +170,7 @@ module Sequel
|
|
155
170
|
SQL_ROLLBACK = 'ROLLBACK'.freeze
|
156
171
|
SQL_RELEASE_SAVEPOINT = 'RELEASE SAVEPOINT autopoint_%d'.freeze
|
157
172
|
SYSTEM_TABLE_REGEXP = /^pg|sql/.freeze
|
173
|
+
TYPES = Sequel::Schema::SQL::TYPES.merge(File=>'bytea', String=>'text')
|
158
174
|
|
159
175
|
# Creates the function in the database. See create_function_sql for arguments.
|
160
176
|
def create_function(*args)
|
@@ -237,11 +253,6 @@ module Sequel
|
|
237
253
|
"CREATE TRIGGER #{name} #{whence} #{events.map{|e| e.to_s.upcase}.join(' OR ')} ON #{quote_schema_table(table)}#{' FOR EACH ROW' if opts[:each_row]} EXECUTE PROCEDURE #{function}(#{Array(opts[:args]).map{|a| literal(a)}.join(', ')})"
|
238
254
|
end
|
239
255
|
|
240
|
-
# The default schema to use if none is specified (default: public)
|
241
|
-
def default_schema
|
242
|
-
@default_schema ||= :public
|
243
|
-
end
|
244
|
-
|
245
256
|
# Drops the function from the database. See drop_function_sql for arguments.
|
246
257
|
def drop_function(*args)
|
247
258
|
self << drop_function_sql(*args)
|
@@ -311,13 +322,13 @@ module Sequel
|
|
311
322
|
filter = " WHERE #{filter_expr(filter)}" if filter
|
312
323
|
case index_type
|
313
324
|
when :full_text
|
314
|
-
cols = Array(index[:columns]).map{|x| :COALESCE
|
325
|
+
cols = Array(index[:columns]).map{|x| SQL::Function.new(:COALESCE, x, '')}.sql_string_join(' ')
|
315
326
|
expr = "(to_tsvector(#{literal(index[:language] || 'simple')}, #{literal(cols)}))"
|
316
327
|
index_type = :gin
|
317
328
|
when :spatial
|
318
329
|
index_type = :gist
|
319
330
|
end
|
320
|
-
"CREATE #{unique}INDEX #{index_name} ON #{table_name} #{"USING #{index_type} " if index_type}#{expr}#{filter}"
|
331
|
+
"CREATE #{unique}INDEX #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{"USING #{index_type} " if index_type}#{expr}#{filter}"
|
321
332
|
end
|
322
333
|
|
323
334
|
# Dataset containing all current database locks
|
@@ -328,8 +339,25 @@ module Sequel
|
|
328
339
|
end
|
329
340
|
|
330
341
|
# Return primary key for the given table.
|
331
|
-
def primary_key(table,
|
332
|
-
|
342
|
+
def primary_key(table, opts={})
|
343
|
+
quoted_table = quote_schema_table(table)
|
344
|
+
return @primary_keys[quoted_table] if @primary_keys.include?(quoted_table)
|
345
|
+
@primary_keys[quoted_table] = if conn = opts[:conn]
|
346
|
+
conn.primary_key(*schema_and_table(table))
|
347
|
+
else
|
348
|
+
synchronize(opts[:server]){|con| con.primary_key(*schema_and_table(table))}
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
# Return the sequence providing the default for the primary key for the given table.
|
353
|
+
def primary_key_sequence(table, opts={})
|
354
|
+
quoted_table = quote_schema_table(table)
|
355
|
+
return @primary_key_sequences[quoted_table] if @primary_key_sequences.include?(quoted_table)
|
356
|
+
@primary_key_sequences[quoted_table] = if conn = opts[:conn]
|
357
|
+
conn.sequence(*schema_and_table(table))
|
358
|
+
else
|
359
|
+
synchronize(opts[:server]){|con| con.sequence(*schema_and_table(table))}
|
360
|
+
end
|
333
361
|
end
|
334
362
|
|
335
363
|
# SQL DDL statement for renaming a table. PostgreSQL doesn't allow you to change a table's schema in
|
@@ -351,7 +379,7 @@ module Sequel
|
|
351
379
|
(conn.server_version rescue nil) if conn.respond_to?(:server_version)
|
352
380
|
end
|
353
381
|
unless @server_version
|
354
|
-
m = /PostgreSQL (\d+)\.(\d+)\.(\d+)/.match(get(:version
|
382
|
+
m = /PostgreSQL (\d+)\.(\d+)\.(\d+)/.match(get(SQL::Function.new(:version)))
|
355
383
|
@server_version = (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
|
356
384
|
end
|
357
385
|
@server_version
|
@@ -365,7 +393,7 @@ module Sequel
|
|
365
393
|
def table_exists?(table, opts={})
|
366
394
|
schema, table = schema_and_table(table)
|
367
395
|
opts[:schema] ||= schema
|
368
|
-
tables(opts){|ds| !ds.first(:relname=>table
|
396
|
+
tables(opts){|ds| !ds.first(:relname=>ds.send(:input_identifier, table)).nil?}
|
369
397
|
end
|
370
398
|
|
371
399
|
# Array of symbols specifying table names in the current database.
|
@@ -376,8 +404,12 @@ module Sequel
|
|
376
404
|
# * :schema - The schema to search (default_schema by default)
|
377
405
|
# * :server - The server to use
|
378
406
|
def tables(opts={})
|
379
|
-
ds = self[:pg_class].
|
380
|
-
|
407
|
+
ds = self[:pg_class].filter(:relkind=>'r').select(:relname).exclude(:relname.like(SYSTEM_TABLE_REGEXP)).server(opts[:server])
|
408
|
+
ds.join!(:pg_namespace, :oid=>:relnamespace, :nspname=>(opts[:schema]||default_schema).to_s) if opts[:schema] || default_schema
|
409
|
+
ds.identifier_input_method = nil
|
410
|
+
ds.identifier_output_method = nil
|
411
|
+
ds2 = dataset
|
412
|
+
block_given? ? yield(ds) : ds.map{|r| ds2.send(:output_identifier, r[:relname])}
|
381
413
|
end
|
382
414
|
|
383
415
|
# PostgreSQL supports multi-level transactions using save points.
|
@@ -424,10 +456,15 @@ module Sequel
|
|
424
456
|
end
|
425
457
|
|
426
458
|
private
|
459
|
+
|
460
|
+
# PostgreSQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on input.
|
461
|
+
def identifier_input_method_default
|
462
|
+
nil
|
463
|
+
end
|
427
464
|
|
428
|
-
# PostgreSQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers
|
429
|
-
def
|
430
|
-
|
465
|
+
# PostgreSQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on output.
|
466
|
+
def identifier_output_method_default
|
467
|
+
nil
|
431
468
|
end
|
432
469
|
|
433
470
|
# The result of the insert for the given table and values. If values
|
@@ -439,12 +476,12 @@ module Sequel
|
|
439
476
|
def insert_result(conn, table, values)
|
440
477
|
case values
|
441
478
|
when Hash
|
442
|
-
return nil unless pk =
|
479
|
+
return nil unless pk = primary_key(table, :conn=>conn)
|
443
480
|
if pk and pkv = values[pk.to_sym]
|
444
481
|
pkv
|
445
482
|
else
|
446
483
|
begin
|
447
|
-
if seq =
|
484
|
+
if seq = primary_key_sequence(table, :conn=>conn)
|
448
485
|
conn.last_insert_id(seq)
|
449
486
|
end
|
450
487
|
rescue Exception => e
|
@@ -464,49 +501,9 @@ module Sequel
|
|
464
501
|
PREPARED_ARG_PLACEHOLDER
|
465
502
|
end
|
466
503
|
|
467
|
-
# Returns primary key for the given table. This information is
|
468
|
-
# cached, and if the primary key for a table is changed, the
|
469
|
-
# @primary_keys instance variable should be reset manually.
|
470
|
-
def primary_key_for_table(conn, table)
|
471
|
-
@primary_keys[quote_schema_table(table)] ||= conn.primary_key(*schema_and_table(table))
|
472
|
-
end
|
473
|
-
|
474
|
-
# Returns primary key for the given table. This information is
|
475
|
-
# cached, and if the primary key for a table is changed, the
|
476
|
-
# @primary_keys instance variable should be reset manually.
|
477
|
-
def primary_key_sequence_for_table(conn, table)
|
478
|
-
@primary_key_sequences[quote_schema_table(table)] ||= conn.sequence(*schema_and_table(table))
|
479
|
-
end
|
480
|
-
|
481
|
-
# Set the default of the row to NULL if it is blank, and set
|
482
|
-
# the ruby type for the column based on the database type.
|
483
|
-
def schema_parse_rows(rows)
|
484
|
-
rows.map do |row|
|
485
|
-
row[:default] = nil if row[:default].blank?
|
486
|
-
row[:type] = schema_column_type(row[:db_type])
|
487
|
-
[row.delete(:name).to_sym, row]
|
488
|
-
end
|
489
|
-
end
|
490
|
-
|
491
|
-
# Parse the schema for a single table.
|
492
|
-
def schema_parse_table(table_name, opts)
|
493
|
-
schema_parse_rows(schema_parser_dataset(table_name, opts))
|
494
|
-
end
|
495
|
-
|
496
|
-
# Parse the schema for multiple tables.
|
497
|
-
def schema_parse_tables(opts)
|
498
|
-
schemas = {}
|
499
|
-
schema_parser_dataset(nil, opts).each do |row|
|
500
|
-
(schemas[quote_schema_table(SQL::QualifiedIdentifier.new(row.delete(:schema), row.delete(:table)))] ||= []) << row
|
501
|
-
end
|
502
|
-
schemas.each do |table, rows|
|
503
|
-
schemas[table] = schema_parse_rows(rows)
|
504
|
-
end
|
505
|
-
schemas
|
506
|
-
end
|
507
|
-
|
508
504
|
# The dataset used for parsing table schemas, using the pg_* system catalogs.
|
509
|
-
def
|
505
|
+
def schema_parse_table(table_name, opts)
|
506
|
+
ds2 = dataset
|
510
507
|
ds = dataset.select(:pg_attribute__attname___name,
|
511
508
|
SQL::Function.new(:format_type, :pg_type__oid, :pg_attribute__atttypmod).as(:db_type),
|
512
509
|
SQL::Function.new(:pg_get_expr, :pg_attrdef__adbin, :pg_class__oid).as(:default),
|
@@ -518,21 +515,28 @@ module Sequel
|
|
518
515
|
left_outer_join(:pg_attrdef, :adrelid=>:pg_class__oid, :adnum=>:pg_attribute__attnum).
|
519
516
|
left_outer_join(:pg_index, :indrelid=>:pg_class__oid, :indisprimary=>true).
|
520
517
|
filter(:pg_attribute__attisdropped=>false).
|
521
|
-
filter
|
518
|
+
filter{|o| o.pg_attribute__attnum > 0}.
|
519
|
+
filter(:pg_class__relname=>ds2.send(:input_identifier, table_name)).
|
522
520
|
order(:pg_attribute__attnum)
|
523
|
-
if
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
521
|
+
ds.join!(:pg_namespace, :oid=>:pg_class__relnamespace, :nspname=>(opts[:schema] || default_schema).to_s) if opts[:schema] || default_schema
|
522
|
+
ds.identifier_input_method = nil
|
523
|
+
ds.identifier_output_method = nil
|
524
|
+
ds.map do |row|
|
525
|
+
row[:default] = nil if row[:default].blank?
|
526
|
+
row[:type] = schema_column_type(row[:db_type])
|
527
|
+
[ds2.send(:output_identifier, row.delete(:name)), row]
|
528
528
|
end
|
529
|
-
ds
|
530
529
|
end
|
531
530
|
|
532
531
|
# Turns an array of argument specifiers into an SQL fragment used for function arguments. See create_function_sql.
|
533
532
|
def sql_function_args(args)
|
534
533
|
"(#{Array(args).map{|a| Array(a).reverse.join(' ')}.join(', ')})"
|
535
534
|
end
|
535
|
+
|
536
|
+
# Override the standard type conversions with PostgreSQL specific ones
|
537
|
+
def type_literal_base(column)
|
538
|
+
TYPES[column[:type]]
|
539
|
+
end
|
536
540
|
end
|
537
541
|
|
538
542
|
# Instance methods for datasets that connect to a PostgreSQL database.
|
@@ -603,7 +607,7 @@ module Sequel
|
|
603
607
|
# in 8.3 by default, and available for earlier versions as an add-on).
|
604
608
|
def full_text_search(cols, terms, opts = {})
|
605
609
|
lang = opts[:language] || 'simple'
|
606
|
-
cols = Array(cols).map{|x| :COALESCE
|
610
|
+
cols = Array(cols).map{|x| SQL::Function.new(:COALESCE, x, '')}.sql_string_join(' ')
|
607
611
|
filter("to_tsvector(#{literal(lang)}, #{literal(cols)}) @@ to_tsquery(#{literal(lang)}, #{literal(Array(terms).join(' | '))})")
|
608
612
|
end
|
609
613
|
|
@@ -634,9 +638,9 @@ module Sequel
|
|
634
638
|
when LiteralString
|
635
639
|
v
|
636
640
|
when SQL::Blob
|
637
|
-
|
641
|
+
"'#{v.gsub(/[\000-\037\047\134\177-\377]/){|b| "\\#{("%o" % b[0..1].unpack("C")[0]).rjust(3, '0')}"}}'"
|
638
642
|
when String
|
639
|
-
|
643
|
+
"'#{v.gsub("'", "''")}'"
|
640
644
|
when Time
|
641
645
|
"#{v.strftime(PG_TIMESTAMP_FORMAT)}.#{sprintf("%06d",v.usec)}'"
|
642
646
|
when DateTime
|