sequel 4.3.0 → 4.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG +34 -0
- data/README.rdoc +7 -7
- data/Rakefile +2 -2
- data/doc/active_record.rdoc +2 -2
- data/doc/association_basics.rdoc +21 -7
- data/doc/bin_sequel.rdoc +2 -2
- data/doc/cheat_sheet.rdoc +2 -1
- data/doc/dataset_basics.rdoc +1 -1
- data/doc/dataset_filtering.rdoc +1 -1
- data/doc/migration.rdoc +2 -2
- data/doc/object_model.rdoc +2 -2
- data/doc/opening_databases.rdoc +13 -1
- data/doc/querying.rdoc +9 -4
- data/doc/release_notes/4.4.0.txt +92 -0
- data/doc/schema_modification.rdoc +1 -1
- data/doc/security.rdoc +2 -2
- data/doc/sql.rdoc +3 -3
- data/doc/thread_safety.rdoc +1 -1
- data/doc/validations.rdoc +1 -1
- data/doc/virtual_rows.rdoc +1 -1
- data/lib/sequel/adapters/jdbc.rb +85 -19
- data/lib/sequel/adapters/jdbc/db2.rb +1 -1
- data/lib/sequel/adapters/jdbc/derby.rb +1 -1
- data/lib/sequel/adapters/jdbc/h2.rb +2 -2
- data/lib/sequel/adapters/jdbc/hsqldb.rb +7 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +1 -1
- data/lib/sequel/adapters/jdbc/oracle.rb +1 -1
- data/lib/sequel/adapters/jdbc/postgresql.rb +34 -3
- data/lib/sequel/adapters/jdbc/sqlanywhere.rb +57 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +2 -2
- data/lib/sequel/adapters/oracle.rb +1 -1
- data/lib/sequel/adapters/shared/db2.rb +5 -0
- data/lib/sequel/adapters/shared/oracle.rb +41 -4
- data/lib/sequel/adapters/shared/sqlanywhere.rb +458 -0
- data/lib/sequel/adapters/sqlanywhere.rb +177 -0
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +11 -3
- data/lib/sequel/core.rb +4 -4
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +1 -1
- data/lib/sequel/database/schema_methods.rb +2 -2
- data/lib/sequel/dataset.rb +1 -1
- data/lib/sequel/dataset/actions.rb +2 -0
- data/lib/sequel/dataset/graph.rb +1 -1
- data/lib/sequel/dataset/prepared_statements.rb +1 -1
- data/lib/sequel/dataset/query.rb +37 -16
- data/lib/sequel/extensions/constraint_validations.rb +1 -1
- data/lib/sequel/extensions/date_arithmetic.rb +2 -2
- data/lib/sequel/extensions/migration.rb +1 -1
- data/lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb +5 -4
- data/lib/sequel/extensions/pg_array.rb +2 -2
- data/lib/sequel/extensions/pg_array_ops.rb +2 -2
- data/lib/sequel/extensions/pg_hstore.rb +2 -2
- data/lib/sequel/extensions/pg_hstore_ops.rb +2 -2
- data/lib/sequel/extensions/pg_json.rb +2 -2
- data/lib/sequel/extensions/pg_json_ops.rb +2 -2
- data/lib/sequel/extensions/pg_range.rb +2 -2
- data/lib/sequel/extensions/pg_range_ops.rb +2 -2
- data/lib/sequel/extensions/pg_row.rb +2 -2
- data/lib/sequel/extensions/pg_row_ops.rb +3 -3
- data/lib/sequel/model.rb +1 -1
- data/lib/sequel/model/associations.rb +106 -17
- data/lib/sequel/model/base.rb +23 -19
- data/lib/sequel/plugins/json_serializer.rb +1 -1
- data/lib/sequel/plugins/many_through_many.rb +14 -6
- data/lib/sequel/plugins/pg_array_associations.rb +28 -0
- data/lib/sequel/plugins/rcte_tree.rb +1 -1
- data/lib/sequel/plugins/serialization.rb +11 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/table_select.rb +41 -0
- data/lib/sequel/plugins/tree.rb +1 -1
- data/lib/sequel/sql.rb +2 -2
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/oracle_spec.rb +22 -1
- data/spec/adapters/postgres_spec.rb +31 -48
- data/spec/adapters/sqlanywhere_spec.rb +170 -0
- data/spec/core/dataset_spec.rb +109 -0
- data/spec/core/object_graph_spec.rb +7 -0
- data/spec/extensions/constraint_validations_spec.rb +7 -0
- data/spec/extensions/core_refinements_spec.rb +1 -1
- data/spec/extensions/many_through_many_spec.rb +65 -0
- data/spec/extensions/pg_array_associations_spec.rb +44 -0
- data/spec/extensions/rcte_tree_spec.rb +3 -3
- data/spec/extensions/spec_helper.rb +1 -1
- data/spec/extensions/table_select_spec.rb +71 -0
- data/spec/integration/associations_test.rb +279 -7
- data/spec/integration/dataset_test.rb +13 -4
- data/spec/integration/schema_test.rb +12 -14
- data/spec/model/associations_spec.rb +472 -3
- data/spec/model/class_dataset_methods_spec.rb +1 -0
- data/spec/model/model_spec.rb +10 -0
- metadata +10 -2
|
@@ -291,7 +291,7 @@ module Sequel
|
|
|
291
291
|
:primary_key => pks.include?(column.name),
|
|
292
292
|
:default => defaults[column.name],
|
|
293
293
|
:oci8_type => column.data_type,
|
|
294
|
-
:db_type => column.type_string
|
|
294
|
+
:db_type => column.type_string,
|
|
295
295
|
:type_string => column.type_string,
|
|
296
296
|
:charset_form => column.charset_form,
|
|
297
297
|
:char_used => column.char_used?,
|
|
@@ -350,6 +350,11 @@ module Sequel
|
|
|
350
350
|
end
|
|
351
351
|
end
|
|
352
352
|
|
|
353
|
+
# DB2 does not require that ROW_NUMBER be ordered.
|
|
354
|
+
def require_offset_order?
|
|
355
|
+
false
|
|
356
|
+
end
|
|
357
|
+
|
|
353
358
|
# Add a fallback table for empty from situation
|
|
354
359
|
def select_from_sql(sql)
|
|
355
360
|
@opts[:from] ? super : (sql << EMPTY_FROM_TABLE)
|
|
@@ -31,6 +31,29 @@ module Sequel
|
|
|
31
31
|
:oracle
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
def foreign_key_list(table, opts=OPTS)
|
|
35
|
+
m = output_identifier_meth
|
|
36
|
+
im = input_identifier_meth
|
|
37
|
+
schema, table = schema_and_table(table)
|
|
38
|
+
ds = metadata_dataset.
|
|
39
|
+
from(:all_cons_columns___pc, :all_constraints___p, :all_cons_columns___fc, :all_constraints___f).
|
|
40
|
+
where(:f__table_name=>im.call(table), :f__constraint_type=>'R', :p__owner=>:f__r_owner, :p__constraint_name=>:f__r_constraint_name, :pc__owner=>:p__owner, :pc__constraint_name=>:p__constraint_name, :pc__table_name=>:p__table_name, :fc__owner=>:f__owner, :fc__constraint_name=>:f__constraint_name, :fc__table_name=>:f__table_name, :fc__position=>:pc__position).
|
|
41
|
+
select(:p__table_name___table, :pc__column_name___key, :fc__column_name___column, :f__constraint_name___name).
|
|
42
|
+
order(:table, :fc__position)
|
|
43
|
+
ds = ds.where(:f__schema_name=>im.call(schema)) if schema
|
|
44
|
+
|
|
45
|
+
fks = {}
|
|
46
|
+
ds.each do |r|
|
|
47
|
+
if fk = fks[r[:name]]
|
|
48
|
+
fk[:columns] << m.call(r[:column])
|
|
49
|
+
fk[:key] << m.call(r[:key])
|
|
50
|
+
else
|
|
51
|
+
fks[r[:name]] = {:name=>m.call(r[:name]), :columns=>[m.call(r[:column])], :table=>m.call(r[:table]), :key=>[m.call(r[:key])]}
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
fks.values
|
|
55
|
+
end
|
|
56
|
+
|
|
34
57
|
# Oracle namespaces indexes per table.
|
|
35
58
|
def global_index_namespace?
|
|
36
59
|
false
|
|
@@ -38,7 +61,7 @@ module Sequel
|
|
|
38
61
|
|
|
39
62
|
def tables(opts=OPTS)
|
|
40
63
|
m = output_identifier_meth
|
|
41
|
-
metadata_dataset.from(:
|
|
64
|
+
metadata_dataset.from(:tabs).server(opts[:server]).select(:table_name).map{|r| m.call(r[:table_name])}
|
|
42
65
|
end
|
|
43
66
|
|
|
44
67
|
def views(opts=OPTS)
|
|
@@ -224,8 +247,6 @@ module Sequel
|
|
|
224
247
|
end
|
|
225
248
|
|
|
226
249
|
module DatasetMethods
|
|
227
|
-
include EmulateOffsetWithRowNumber
|
|
228
|
-
|
|
229
250
|
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with select distinct columns from join where group having compounds order lock')
|
|
230
251
|
ROW_NUMBER_EXPRESSION = LiteralString.new('ROWNUM').freeze
|
|
231
252
|
SPACE = Dataset::SPACE
|
|
@@ -326,7 +347,23 @@ module Sequel
|
|
|
326
347
|
|
|
327
348
|
# Handle LIMIT by using a unlimited subselect filtered with ROWNUM.
|
|
328
349
|
def select_sql
|
|
329
|
-
|
|
350
|
+
return super if @opts[:sql]
|
|
351
|
+
if o = @opts[:offset]
|
|
352
|
+
columns = clone(:append_sql=>'').columns
|
|
353
|
+
dsa1 = dataset_alias(1)
|
|
354
|
+
rn = row_number_column
|
|
355
|
+
limit = @opts[:limit]
|
|
356
|
+
ds = unlimited.
|
|
357
|
+
from_self(:alias=>dsa1).
|
|
358
|
+
select_append(ROW_NUMBER_EXPRESSION.as(rn)).
|
|
359
|
+
from_self(:alias=>dsa1).
|
|
360
|
+
select(*columns).
|
|
361
|
+
where(SQL::Identifier.new(rn) > o)
|
|
362
|
+
ds = ds.where(SQL::Identifier.new(rn) <= Sequel.+(o, limit)) if limit
|
|
363
|
+
sql = @opts[:append_sql] || ''
|
|
364
|
+
subselect_sql_append(sql, ds)
|
|
365
|
+
sql
|
|
366
|
+
elsif limit = @opts[:limit]
|
|
330
367
|
ds = clone(:limit=>nil)
|
|
331
368
|
# Lock doesn't work in subselects, so don't use a subselect when locking.
|
|
332
369
|
# Don't use a subselect if custom SQL is used, as it breaks somethings.
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module SqlAnywhere
|
|
3
|
+
|
|
4
|
+
@convert_smallint_to_bool = true
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
# Whether to convert smallint values to bool, false by default.
|
|
8
|
+
# Can also be overridden per dataset.
|
|
9
|
+
attr_accessor :convert_smallint_to_bool
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module DatabaseMethods
|
|
13
|
+
extend Sequel::Database::ResetIdentifierMangling
|
|
14
|
+
|
|
15
|
+
attr_reader :conversion_procs
|
|
16
|
+
|
|
17
|
+
# Override the default SqlAnywhere.convert_smallint_to_bool setting for this database.
|
|
18
|
+
attr_writer :convert_smallint_to_bool
|
|
19
|
+
|
|
20
|
+
AUTO_INCREMENT = 'IDENTITY'.freeze
|
|
21
|
+
SQL_BEGIN = "BEGIN TRANSACTION".freeze
|
|
22
|
+
SQL_COMMIT = "COMMIT TRANSACTION".freeze
|
|
23
|
+
SQL_ROLLBACK = "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION".freeze
|
|
24
|
+
TEMPORARY = "GLOBAL TEMPORARY ".freeze
|
|
25
|
+
SMALLINT_RE = /smallint/i.freeze
|
|
26
|
+
DECIMAL_TYPE_RE = /numeric/io
|
|
27
|
+
|
|
28
|
+
# Whether to convert smallint to boolean arguments for this dataset.
|
|
29
|
+
# Defaults to the SqlAnywhere module setting.
|
|
30
|
+
def convert_smallint_to_bool
|
|
31
|
+
defined?(@convert_smallint_to_bool) ? @convert_smallint_to_bool : (@convert_smallint_to_bool = ::Sequel::SqlAnywhere.convert_smallint_to_bool)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Sysbase Server uses the :sqlanywhere type.
|
|
35
|
+
def database_type
|
|
36
|
+
:sqlanywhere
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_application_timestamp_sa(v)
|
|
40
|
+
to_application_timestamp(v.to_s) if v
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Convert smallint type to boolean if convert_smallint_to_bool is true
|
|
44
|
+
def schema_column_type(db_type)
|
|
45
|
+
if convert_smallint_to_bool && db_type =~ SMALLINT_RE
|
|
46
|
+
:boolean
|
|
47
|
+
else
|
|
48
|
+
super
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def schema_parse_table(table, opts)
|
|
53
|
+
m = output_identifier_meth(opts[:dataset])
|
|
54
|
+
im = input_identifier_meth(opts[:dataset])
|
|
55
|
+
metadata_dataset.
|
|
56
|
+
from{sa_describe_query("select * from #{im.call(table)}").as(:a)}.
|
|
57
|
+
join(:syscolumn___b, :table_id=>:base_table_id, :column_id=>:base_column_id).
|
|
58
|
+
order(:a__column_number).
|
|
59
|
+
map do |row|
|
|
60
|
+
row[:auto_increment] = row.delete(:is_autoincrement) == 1
|
|
61
|
+
row[:primary_key] = row.delete(:pkey) == 'Y'
|
|
62
|
+
row[:allow_null] = row[:nulls_allowed].is_a?(Fixnum) ? row.delete(:nulls_allowed) == 1 : row.delete(:nulls_allowed)
|
|
63
|
+
row[:db_type] = row.delete(:domain_name)
|
|
64
|
+
row[:type] = if row[:db_type] =~ DECIMAL_TYPE_RE and (row[:scale].is_a?(Fixnum) ? row[:scale] == 0 : !row[:scale])
|
|
65
|
+
:integer
|
|
66
|
+
else
|
|
67
|
+
schema_column_type(row[:db_type])
|
|
68
|
+
end
|
|
69
|
+
[m.call(row.delete(:name)), row]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def indexes(table, opts = OPTS)
|
|
74
|
+
m = output_identifier_meth
|
|
75
|
+
im = input_identifier_meth
|
|
76
|
+
indexes = {}
|
|
77
|
+
metadata_dataset.
|
|
78
|
+
from(:dbo__sysobjects___z).
|
|
79
|
+
select(:z__name___table_name, :i__name___index_name, :si__indextype___type, :si__colnames___columns).
|
|
80
|
+
join(:dbo__sysindexes___i, :id___i=> :id___z).
|
|
81
|
+
join(:sys__sysindexes___si, :iname=> :name___i).
|
|
82
|
+
where(:z__type => 'U', :table_name=>im.call(table)).
|
|
83
|
+
each do |r|
|
|
84
|
+
indexes[m.call(r[:index_name])] =
|
|
85
|
+
{:unique=>(r[:type].downcase=='unique'),
|
|
86
|
+
:columns=>r[:columns].split(',').map{|v| m.call(v.split(' ').first)}} unless r[:type].downcase == 'primary key'
|
|
87
|
+
end
|
|
88
|
+
indexes
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def foreign_key_list(table, opts=OPTS)
|
|
92
|
+
m = output_identifier_meth
|
|
93
|
+
im = input_identifier_meth
|
|
94
|
+
fk_indexes = {}
|
|
95
|
+
metadata_dataset.
|
|
96
|
+
from(:sys__sysforeignkey___fk).
|
|
97
|
+
select(:fk__role___name, :fks__columns___column_map, :si__indextype___type, :si__colnames___columns, :fks__primary_tname___table_name).
|
|
98
|
+
join(:sys__sysforeignkeys___fks, :role => :role).
|
|
99
|
+
join_table(:inner, :sys__sysindexes___si, [:iname=> :fk__role], {:implicit_qualifier => :fk}).
|
|
100
|
+
where(:fks__foreign_tname=>im.call(table)).
|
|
101
|
+
each do |r|
|
|
102
|
+
unless r[:type].downcase == 'primary key'
|
|
103
|
+
fk_indexes[r[:name]] =
|
|
104
|
+
{:name=>m.call(r[:name]),
|
|
105
|
+
:columns=>r[:columns].split(',').map{|v| m.call(v.split(' ').first)},
|
|
106
|
+
:table=>m.call(r[:table_name]),
|
|
107
|
+
:key=>r[:column_map].split(',').map{|v| m.call(v.split(' IS ').last)}}
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
fk_indexes.values
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def tables(opts=OPTS)
|
|
114
|
+
tables_and_views('U', opts)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def views(opts=OPTS)
|
|
118
|
+
tables_and_views('V', opts)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
DATABASE_ERROR_REGEXPS = {
|
|
124
|
+
/would not be unique/ => Sequel::UniqueConstraintViolation,
|
|
125
|
+
/Column .* in table .* cannot be NULL/ => Sequel::NotNullConstraintViolation,
|
|
126
|
+
/Constraint .* violated: Invalid value in table .*/ => Sequel::CheckConstraintViolation,
|
|
127
|
+
/No primary key value for foreign key .* in table .*/ => Sequel::ForeignKeyConstraintViolation,
|
|
128
|
+
/Primary key for row in table .* is referenced by foreign key .* in table .*/ => Sequel::ForeignKeyConstraintViolation
|
|
129
|
+
}.freeze
|
|
130
|
+
|
|
131
|
+
def database_error_regexps
|
|
132
|
+
DATABASE_ERROR_REGEXPS
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Sybase uses the IDENTITY column for autoincrementing columns.
|
|
136
|
+
def auto_increment_sql
|
|
137
|
+
AUTO_INCREMENT
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# SQL fragment for marking a table as temporary
|
|
141
|
+
def temporary_table_sql
|
|
142
|
+
TEMPORARY
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# SQL to BEGIN a transaction.
|
|
146
|
+
def begin_transaction_sql
|
|
147
|
+
SQL_BEGIN
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# SQL to ROLLBACK a transaction.
|
|
151
|
+
def rollback_transaction_sql
|
|
152
|
+
SQL_ROLLBACK
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# SQL to COMMIT a transaction.
|
|
156
|
+
def commit_transaction_sql
|
|
157
|
+
SQL_COMMIT
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Sybase has both datetime and timestamp classes, most people are going
|
|
161
|
+
# to want datetime
|
|
162
|
+
def type_literal_generic_datetime(column)
|
|
163
|
+
:datetime
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Sybase has both datetime and timestamp classes, most people are going
|
|
167
|
+
# to want datetime
|
|
168
|
+
def type_literal_generic_time(column)
|
|
169
|
+
column[:only_time] ? :time : :datetime
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Sybase doesn't have a true boolean class, so it uses integer
|
|
173
|
+
def type_literal_generic_trueclass(column)
|
|
174
|
+
:smallint
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# SQLAnywhere uses image type for blobs
|
|
178
|
+
def type_literal_generic_file(column)
|
|
179
|
+
:image
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Sybase specific syntax for altering tables.
|
|
183
|
+
def alter_table_sql(table, op)
|
|
184
|
+
case op[:op]
|
|
185
|
+
when :add_column
|
|
186
|
+
"ALTER TABLE #{quote_schema_table(table)} ADD #{column_definition_sql(op)}"
|
|
187
|
+
when :drop_column
|
|
188
|
+
"ALTER TABLE #{quote_schema_table(table)} DROP #{column_definition_sql(op)}"
|
|
189
|
+
when :drop_constraint
|
|
190
|
+
case op[:type]
|
|
191
|
+
when :primary_key
|
|
192
|
+
"ALTER TABLE #{quote_schema_table(table)} DROP PRIMARY KEY"
|
|
193
|
+
when :foreign_key
|
|
194
|
+
if op[:name] || op[:columns]
|
|
195
|
+
name = op[:name] || foreign_key_name(table, op[:columns])
|
|
196
|
+
if name
|
|
197
|
+
"ALTER TABLE #{quote_schema_table(table)} DROP FOREIGN KEY #{quote_identifier(name)}"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
else
|
|
201
|
+
super
|
|
202
|
+
end
|
|
203
|
+
when :rename_column
|
|
204
|
+
"ALTER TABLE #{quote_schema_table(table)} RENAME #{quote_identifier(op[:name])} TO #{quote_identifier(op[:new_name].to_s)}"
|
|
205
|
+
when :set_column_type
|
|
206
|
+
"ALTER TABLE #{quote_schema_table(table)} ALTER #{quote_identifier(op[:name])} #{type_literal(op)}"
|
|
207
|
+
when :set_column_null
|
|
208
|
+
"ALTER TABLE #{quote_schema_table(table)} ALTER #{quote_identifier(op[:name])} #{'NOT ' unless op[:null]}NULL"
|
|
209
|
+
when :set_column_default
|
|
210
|
+
"ALTER TABLE #{quote_schema_table(table)} ALTER #{quote_identifier(op[:name])} DEFAULT #{literal(op[:default])}"
|
|
211
|
+
else
|
|
212
|
+
super(table, op)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# SqlAnywhere doesn't support CREATE TABLE AS, it only supports SELECT INTO.
|
|
217
|
+
# Emulating CREATE TABLE AS using SELECT INTO is only possible if a dataset
|
|
218
|
+
# is given as the argument, it can't work with a string, so raise an
|
|
219
|
+
# Error if a string is given.
|
|
220
|
+
def create_table_as(name, ds, options)
|
|
221
|
+
raise(Error, "must provide dataset instance as value of create_table :as option on SqlAnywhere") unless ds.is_a?(Sequel::Dataset)
|
|
222
|
+
run(ds.into(name).sql)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Use SP_RENAME to rename the table
|
|
226
|
+
def rename_table_sql(name, new_name)
|
|
227
|
+
"ALTER TABLE #{quote_schema_table(name)} RENAME #{quote_schema_table(new_name)}"
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def tables_and_views(type, opts=OPTS)
|
|
231
|
+
m = output_identifier_meth
|
|
232
|
+
metadata_dataset.
|
|
233
|
+
from(:sysobjects___a).
|
|
234
|
+
where(:a__type=>type).
|
|
235
|
+
select_map(:a__name).
|
|
236
|
+
map{|n| m.call(n)}
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
module DatasetMethods
|
|
241
|
+
BOOL_TRUE = '1'.freeze
|
|
242
|
+
BOOL_FALSE = '0'.freeze
|
|
243
|
+
INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'with insert into columns values')
|
|
244
|
+
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with select distinct limit columns into from join where group having order compounds lock')
|
|
245
|
+
WILDCARD = LiteralString.new('%').freeze
|
|
246
|
+
TOP = " TOP ".freeze
|
|
247
|
+
START_AT = " START AT ".freeze
|
|
248
|
+
SQL_WITH_RECURSIVE = "WITH RECURSIVE ".freeze
|
|
249
|
+
DATE_FUNCTION = 'today()'.freeze
|
|
250
|
+
NOW_FUNCTION = 'now()'.freeze
|
|
251
|
+
DATEPART = 'datepart'.freeze
|
|
252
|
+
REGEXP = 'REGEXP'.freeze
|
|
253
|
+
NOT_REGEXP = 'NOT REGEXP'.freeze
|
|
254
|
+
TIMESTAMP_USEC_FORMAT = ".%03d".freeze
|
|
255
|
+
APOS = Dataset::APOS
|
|
256
|
+
APOS_RE = Dataset::APOS_RE
|
|
257
|
+
DOUBLE_APOS = Dataset::DOUBLE_APOS
|
|
258
|
+
BACKSLASH_RE = /\\/.freeze
|
|
259
|
+
QUAD_BACKSLASH = "\\\\\\\\".freeze
|
|
260
|
+
BLOB_START = "0x".freeze
|
|
261
|
+
HSTAR = "H*".freeze
|
|
262
|
+
CROSS_APPLY = 'CROSS APPLY'.freeze
|
|
263
|
+
OUTER_APPLY = 'OUTER APPLY'.freeze
|
|
264
|
+
|
|
265
|
+
# Whether to convert smallint to boolean arguments for this dataset.
|
|
266
|
+
# Defaults to the SqlAnywhere module setting.
|
|
267
|
+
def convert_smallint_to_bool
|
|
268
|
+
defined?(@convert_smallint_to_bool) ? @convert_smallint_to_bool : (@convert_smallint_to_bool = @db.convert_smallint_to_bool)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Override the default SqlAnywhere.convert_smallint_to_bool setting for this dataset.
|
|
272
|
+
attr_writer :convert_smallint_to_bool
|
|
273
|
+
|
|
274
|
+
def supports_multiple_column_in?
|
|
275
|
+
false
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def supports_where_true?
|
|
279
|
+
false
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def supports_is_true?
|
|
283
|
+
false
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def supports_join_using?
|
|
287
|
+
false
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def supports_timestamp_usecs?
|
|
291
|
+
false
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Uses CROSS APPLY to join the given table into the current dataset.
|
|
295
|
+
def cross_apply(table)
|
|
296
|
+
join_table(:cross_apply, table)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# SqlAnywhere requires recursive CTEs to have column aliases.
|
|
300
|
+
def recursive_cte_requires_column_aliases?
|
|
301
|
+
true
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# SQLAnywhere uses + for string concatenation, and LIKE is case insensitive by default.
|
|
305
|
+
def complex_expression_sql_append(sql, op, args)
|
|
306
|
+
case op
|
|
307
|
+
when :'||'
|
|
308
|
+
super(sql, :+, args)
|
|
309
|
+
when :<<
|
|
310
|
+
sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
|
|
311
|
+
when :>>
|
|
312
|
+
sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
|
|
313
|
+
when :LIKE, :"NOT LIKE"
|
|
314
|
+
sql << Sequel::Dataset::PAREN_OPEN
|
|
315
|
+
literal_append(sql, args.at(0))
|
|
316
|
+
sql << Sequel::Dataset::SPACE << (op == :LIKE ? REGEXP : NOT_REGEXP) << Sequel::Dataset::SPACE
|
|
317
|
+
pattern = ''
|
|
318
|
+
last_c = ''
|
|
319
|
+
args.at(1).each_char do |c|
|
|
320
|
+
if c == '_' and not pattern.end_with?('\\') and last_c != '\\'
|
|
321
|
+
pattern << '.'
|
|
322
|
+
elsif c == '%' and not pattern.end_with?('\\') and last_c != '\\'
|
|
323
|
+
pattern << '.*'
|
|
324
|
+
elsif c == '[' and not pattern.end_with?('\\') and last_c != '\\'
|
|
325
|
+
pattern << '\['
|
|
326
|
+
elsif c == ']' and not pattern.end_with?('\\') and last_c != '\\'
|
|
327
|
+
pattern << '\]'
|
|
328
|
+
elsif c == '*' and not pattern.end_with?('\\') and last_c != '\\'
|
|
329
|
+
pattern << '\*'
|
|
330
|
+
elsif c == '?' and not pattern.end_with?('\\') and last_c != '\\'
|
|
331
|
+
pattern << '\?'
|
|
332
|
+
else
|
|
333
|
+
pattern << c
|
|
334
|
+
end
|
|
335
|
+
if c == '\\' and last_c == '\\'
|
|
336
|
+
last_c = ''
|
|
337
|
+
else
|
|
338
|
+
last_c = c
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
literal_append(sql, pattern)
|
|
342
|
+
sql << Sequel::Dataset::ESCAPE
|
|
343
|
+
literal_append(sql, Sequel::Dataset::BACKSLASH)
|
|
344
|
+
sql << Sequel::Dataset::PAREN_CLOSE
|
|
345
|
+
when :ILIKE, :"NOT ILIKE"
|
|
346
|
+
super(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), args)
|
|
347
|
+
when :extract
|
|
348
|
+
sql << DATEPART + Sequel::Dataset::PAREN_OPEN
|
|
349
|
+
literal_append(sql, args.at(0))
|
|
350
|
+
sql << ','
|
|
351
|
+
literal_append(sql, args.at(1))
|
|
352
|
+
sql << Sequel::Dataset::PAREN_CLOSE
|
|
353
|
+
else
|
|
354
|
+
super
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# SqlAnywhere uses \\ to escape metacharacters, but a ']' should not be escaped
|
|
359
|
+
def escape_like(string)
|
|
360
|
+
string.gsub(/[\\%_\[]/){|m| "\\#{m}"}
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Use Date() and Now() for CURRENT_DATE and CURRENT_TIMESTAMP
|
|
364
|
+
def constant_sql_append(sql, constant)
|
|
365
|
+
case constant
|
|
366
|
+
when :CURRENT_DATE
|
|
367
|
+
sql << DATE_FUNCTION
|
|
368
|
+
when :CURRENT_TIMESTAMP, :CURRENT_TIME
|
|
369
|
+
sql << NOW_FUNCTION
|
|
370
|
+
else
|
|
371
|
+
super
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# Specify a table for a SELECT ... INTO query.
|
|
376
|
+
def into(table)
|
|
377
|
+
clone(:into => table)
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
private
|
|
381
|
+
|
|
382
|
+
# Use 1 for true on Sybase
|
|
383
|
+
def literal_true
|
|
384
|
+
BOOL_TRUE
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# Use 0 for false on Sybase
|
|
388
|
+
def literal_false
|
|
389
|
+
BOOL_FALSE
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# SQL fragment for String. Doubles \ and ' by default.
|
|
393
|
+
def literal_string_append(sql, v)
|
|
394
|
+
sql << APOS << v.gsub(BACKSLASH_RE, QUAD_BACKSLASH).gsub(APOS_RE, DOUBLE_APOS) << APOS
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# SqlAnywhere uses a preceding X for hex escaping strings
|
|
398
|
+
def literal_blob_append(sql, v)
|
|
399
|
+
if v.empty?
|
|
400
|
+
literal_append(sql, "")
|
|
401
|
+
else
|
|
402
|
+
sql << BLOB_START << v.unpack(HSTAR).first
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# Sybase supports the OUTPUT clause for INSERT statements.
|
|
407
|
+
# It also allows prepending a WITH clause.
|
|
408
|
+
def insert_clause_methods
|
|
409
|
+
INSERT_CLAUSE_METHODS
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def select_clause_methods
|
|
413
|
+
SELECT_CLAUSE_METHODS
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def select_into_sql(sql)
|
|
417
|
+
if i = @opts[:into]
|
|
418
|
+
sql << Sequel::Dataset::INTO
|
|
419
|
+
identifier_append(sql, i)
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def format_timestamp_usec(usec)
|
|
424
|
+
sprintf(TIMESTAMP_USEC_FORMAT, usec/1000)
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# Sybase uses TOP N for limit. For Sybase TOP (N) is used
|
|
428
|
+
# to allow the limit to be a bound variable.
|
|
429
|
+
def select_limit_sql(sql)
|
|
430
|
+
if l = @opts[:limit]
|
|
431
|
+
sql << TOP
|
|
432
|
+
literal_append(sql, l)
|
|
433
|
+
end
|
|
434
|
+
if o = @opts[:offset]
|
|
435
|
+
sql << START_AT + "("
|
|
436
|
+
literal_append(sql, o)
|
|
437
|
+
sql << " + 1)"
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Use WITH RECURSIVE instead of WITH if any of the CTEs is recursive
|
|
442
|
+
def select_with_sql_base
|
|
443
|
+
opts[:with].any?{|w| w[:recursive]} ? SQL_WITH_RECURSIVE : super
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def join_type_sql(join_type)
|
|
447
|
+
case join_type
|
|
448
|
+
when :cross_apply
|
|
449
|
+
CROSS_APPLY
|
|
450
|
+
when :outer_apply
|
|
451
|
+
OUTER_APPLY
|
|
452
|
+
else
|
|
453
|
+
super
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
end
|