sequel 3.21.0 → 3.28.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 +294 -0
- data/README.rdoc +20 -6
- data/Rakefile +20 -15
- data/doc/association_basics.rdoc +210 -43
- data/doc/dataset_basics.rdoc +4 -4
- data/doc/mass_assignment.rdoc +54 -0
- data/doc/migration.rdoc +15 -538
- data/doc/model_hooks.rdoc +64 -27
- data/doc/opening_databases.rdoc +37 -10
- data/doc/prepared_statements.rdoc +16 -10
- data/doc/reflection.rdoc +8 -2
- data/doc/release_notes/3.22.0.txt +39 -0
- data/doc/release_notes/3.23.0.txt +172 -0
- data/doc/release_notes/3.24.0.txt +420 -0
- data/doc/release_notes/3.25.0.txt +88 -0
- data/doc/release_notes/3.26.0.txt +88 -0
- data/doc/release_notes/3.27.0.txt +82 -0
- data/doc/release_notes/3.28.0.txt +304 -0
- data/doc/schema_modification.rdoc +547 -0
- data/doc/testing.rdoc +106 -0
- data/doc/transactions.rdoc +97 -0
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/adapters/ado.rb +12 -1
- data/lib/sequel/adapters/amalgalite.rb +4 -0
- data/lib/sequel/adapters/db2.rb +95 -58
- data/lib/sequel/adapters/do.rb +12 -0
- data/lib/sequel/adapters/firebird.rb +25 -203
- data/lib/sequel/adapters/ibmdb.rb +440 -0
- data/lib/sequel/adapters/informix.rb +4 -19
- data/lib/sequel/adapters/jdbc/as400.rb +0 -7
- data/lib/sequel/adapters/jdbc/db2.rb +49 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
- data/lib/sequel/adapters/jdbc/h2.rb +16 -5
- data/lib/sequel/adapters/jdbc/informix.rb +31 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
- data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
- data/lib/sequel/adapters/jdbc/postgresql.rb +6 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
- data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
- data/lib/sequel/adapters/jdbc.rb +62 -29
- data/lib/sequel/adapters/mysql.rb +22 -139
- data/lib/sequel/adapters/mysql2.rb +9 -14
- data/lib/sequel/adapters/odbc/db2.rb +21 -0
- data/lib/sequel/adapters/odbc.rb +15 -3
- data/lib/sequel/adapters/oracle.rb +17 -1
- data/lib/sequel/adapters/postgres.rb +111 -16
- data/lib/sequel/adapters/shared/access.rb +21 -0
- data/lib/sequel/adapters/shared/db2.rb +290 -0
- data/lib/sequel/adapters/shared/firebird.rb +214 -0
- data/lib/sequel/adapters/shared/informix.rb +45 -0
- data/lib/sequel/adapters/shared/mssql.rb +85 -47
- data/lib/sequel/adapters/shared/mysql.rb +50 -7
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +143 -0
- data/lib/sequel/adapters/shared/oracle.rb +0 -4
- data/lib/sequel/adapters/shared/postgres.rb +75 -43
- data/lib/sequel/adapters/shared/sqlite.rb +56 -8
- data/lib/sequel/adapters/sqlite.rb +12 -11
- data/lib/sequel/adapters/swift/mysql.rb +9 -0
- data/lib/sequel/adapters/tinytds.rb +139 -7
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
- data/lib/sequel/ast_transformer.rb +190 -0
- data/lib/sequel/connection_pool/threaded.rb +3 -2
- data/lib/sequel/connection_pool.rb +1 -1
- data/lib/sequel/core.rb +6 -5
- data/lib/sequel/database/connecting.rb +5 -5
- data/lib/sequel/database/dataset.rb +1 -1
- data/lib/sequel/database/dataset_defaults.rb +1 -1
- data/lib/sequel/database/logging.rb +1 -1
- data/lib/sequel/database/misc.rb +38 -17
- data/lib/sequel/database/query.rb +50 -19
- data/lib/sequel/database/schema_generator.rb +8 -5
- data/lib/sequel/database/schema_methods.rb +52 -27
- data/lib/sequel/dataset/actions.rb +167 -48
- data/lib/sequel/dataset/features.rb +57 -8
- data/lib/sequel/dataset/graph.rb +1 -1
- data/lib/sequel/dataset/misc.rb +39 -20
- data/lib/sequel/dataset/mutation.rb +3 -3
- data/lib/sequel/dataset/prepared_statements.rb +29 -14
- data/lib/sequel/dataset/query.rb +182 -32
- data/lib/sequel/dataset/sql.rb +31 -58
- data/lib/sequel/dataset.rb +8 -0
- data/lib/sequel/exceptions.rb +4 -0
- data/lib/sequel/extensions/columns_introspection.rb +61 -0
- data/lib/sequel/extensions/migration.rb +6 -4
- data/lib/sequel/extensions/to_dot.rb +95 -83
- data/lib/sequel/model/associations.rb +893 -309
- data/lib/sequel/model/base.rb +302 -105
- data/lib/sequel/model/errors.rb +1 -1
- data/lib/sequel/model/exceptions.rb +5 -1
- data/lib/sequel/model.rb +13 -7
- data/lib/sequel/plugins/association_pks.rb +22 -4
- data/lib/sequel/plugins/defaults_setter.rb +58 -0
- data/lib/sequel/plugins/identity_map.rb +113 -6
- data/lib/sequel/plugins/many_through_many.rb +67 -5
- data/lib/sequel/plugins/prepared_statements.rb +140 -0
- data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
- data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
- data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
- data/lib/sequel/plugins/sharding.rb +12 -20
- data/lib/sequel/plugins/single_table_inheritance.rb +2 -0
- data/lib/sequel/plugins/update_primary_key.rb +1 -1
- data/lib/sequel/plugins/xml_serializer.rb +3 -3
- data/lib/sequel/sql.rb +107 -51
- data/lib/sequel/timezones.rb +12 -3
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +146 -0
- data/spec/adapters/mssql_spec.rb +36 -0
- data/spec/adapters/mysql_spec.rb +36 -19
- data/spec/adapters/postgres_spec.rb +115 -28
- data/spec/adapters/spec_helper.rb +6 -0
- data/spec/adapters/sqlite_spec.rb +11 -0
- data/spec/core/connection_pool_spec.rb +62 -77
- data/spec/core/database_spec.rb +244 -287
- data/spec/core/dataset_spec.rb +383 -34
- data/spec/core/expression_filters_spec.rb +159 -41
- data/spec/core/schema_spec.rb +326 -3
- data/spec/core/spec_helper.rb +45 -0
- data/spec/extensions/association_pks_spec.rb +38 -0
- data/spec/extensions/columns_introspection_spec.rb +91 -0
- data/spec/extensions/defaults_setter_spec.rb +64 -0
- data/spec/extensions/identity_map_spec.rb +162 -0
- data/spec/extensions/many_through_many_spec.rb +195 -20
- data/spec/extensions/migration_spec.rb +17 -17
- data/spec/extensions/nested_attributes_spec.rb +1 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
- data/spec/extensions/prepared_statements_spec.rb +72 -0
- data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
- data/spec/extensions/schema_dumper_spec.rb +2 -2
- data/spec/extensions/schema_spec.rb +12 -20
- data/spec/extensions/serialization_modification_detection_spec.rb +36 -0
- data/spec/extensions/single_table_inheritance_spec.rb +11 -0
- data/spec/extensions/spec_helper.rb +3 -1
- data/spec/extensions/to_dot_spec.rb +3 -5
- data/spec/extensions/xml_serializer_spec.rb +16 -4
- data/spec/integration/associations_test.rb +405 -15
- data/spec/integration/database_test.rb +4 -2
- data/spec/integration/dataset_test.rb +240 -20
- data/spec/integration/plugin_test.rb +142 -5
- data/spec/integration/prepared_statement_test.rb +174 -95
- data/spec/integration/schema_test.rb +128 -16
- data/spec/integration/spec_helper.rb +15 -0
- data/spec/integration/transaction_test.rb +40 -0
- data/spec/integration/type_test.rb +16 -2
- data/spec/model/association_reflection_spec.rb +91 -0
- data/spec/model/associations_spec.rb +476 -5
- data/spec/model/base_spec.rb +91 -1
- data/spec/model/eager_loading_spec.rb +519 -31
- data/spec/model/hooks_spec.rb +161 -0
- data/spec/model/model_spec.rb +89 -2
- data/spec/model/plugins_spec.rb +17 -0
- data/spec/model/record_spec.rb +184 -12
- data/spec/model/spec_helper.rb +5 -0
- data/spec/model/validations_spec.rb +11 -0
- metadata +85 -34
|
@@ -358,6 +358,11 @@ module Sequel
|
|
|
358
358
|
@supports_prepared_transactions = self['SHOW max_prepared_transactions'].get.to_i > 0
|
|
359
359
|
end
|
|
360
360
|
|
|
361
|
+
# PostgreSQL supports CREATE TABLE IF NOT EXISTS on 9.1+
|
|
362
|
+
def supports_create_table_if_not_exists?
|
|
363
|
+
server_version >= 90100
|
|
364
|
+
end
|
|
365
|
+
|
|
361
366
|
# PostgreSQL supports savepoints
|
|
362
367
|
def supports_savepoints?
|
|
363
368
|
true
|
|
@@ -368,30 +373,24 @@ module Sequel
|
|
|
368
373
|
true
|
|
369
374
|
end
|
|
370
375
|
|
|
371
|
-
#
|
|
376
|
+
# Array of symbols specifying table names in the current database.
|
|
377
|
+
# The dataset used is yielded to the block if one is provided,
|
|
378
|
+
# otherwise, an array of symbols of table names is returned.
|
|
372
379
|
#
|
|
373
380
|
# Options:
|
|
374
381
|
# * :schema - The schema to search (default_schema by default)
|
|
375
382
|
# * :server - The server to use
|
|
376
|
-
def
|
|
377
|
-
|
|
378
|
-
schema, table = schema_and_table(table)
|
|
379
|
-
opts[:schema] ||= schema
|
|
380
|
-
tables(opts){|ds| !ds.first(:relname=>im.call(table)).nil?}
|
|
383
|
+
def tables(opts={}, &block)
|
|
384
|
+
pg_class_relname('r', opts, &block)
|
|
381
385
|
end
|
|
382
|
-
|
|
383
|
-
# Array of symbols specifying
|
|
384
|
-
# The dataset used is yielded to the block if one is provided,
|
|
385
|
-
# otherwise, an array of symbols of table names is returned.
|
|
386
|
+
|
|
387
|
+
# Array of symbols specifying view names in the current database.
|
|
386
388
|
#
|
|
387
389
|
# Options:
|
|
388
390
|
# * :schema - The schema to search (default_schema by default)
|
|
389
391
|
# * :server - The server to use
|
|
390
|
-
def
|
|
391
|
-
|
|
392
|
-
ds = filter_schema(ds, opts)
|
|
393
|
-
m = output_identifier_meth
|
|
394
|
-
block_given? ? yield(ds) : ds.map{|r| m.call(r[:relname])}
|
|
392
|
+
def views(opts={})
|
|
393
|
+
pg_class_relname('v', opts)
|
|
395
394
|
end
|
|
396
395
|
|
|
397
396
|
private
|
|
@@ -454,11 +453,6 @@ module Sequel
|
|
|
454
453
|
"DROP LANGUAGE#{' IF EXISTS' if opts[:if_exists]} #{name}#{' CASCADE' if opts[:cascade]}"
|
|
455
454
|
end
|
|
456
455
|
|
|
457
|
-
# Always CASCADE the table drop
|
|
458
|
-
def drop_table_sql(name)
|
|
459
|
-
"DROP TABLE #{quote_schema_table(name)} CASCADE"
|
|
460
|
-
end
|
|
461
|
-
|
|
462
456
|
# SQL for dropping a trigger from the database.
|
|
463
457
|
def drop_trigger_sql(table, name, opts={})
|
|
464
458
|
"DROP TRIGGER#{' IF EXISTS' if opts[:if_exists]} #{name} ON #{quote_schema_table(table)}#{' CASCADE' if opts[:cascade]}"
|
|
@@ -540,6 +534,14 @@ module Sequel
|
|
|
540
534
|
conn.execute(sql)
|
|
541
535
|
end
|
|
542
536
|
|
|
537
|
+
# Backbone of the tables and views support.
|
|
538
|
+
def pg_class_relname(type, opts)
|
|
539
|
+
ds = metadata_dataset.from(:pg_class).filter(:relkind=>type).select(:relname).exclude(SQL::StringExpression.like(:relname, SYSTEM_TABLE_REGEXP)).server(opts[:server]).join(:pg_namespace, :oid=>:relnamespace)
|
|
540
|
+
ds = filter_schema(ds, opts)
|
|
541
|
+
m = output_identifier_meth
|
|
542
|
+
block_given? ? yield(ds) : ds.map{|r| m.call(r[:relname])}
|
|
543
|
+
end
|
|
544
|
+
|
|
543
545
|
# Use a dollar sign instead of question mark for the argument
|
|
544
546
|
# placeholder.
|
|
545
547
|
def prepared_arg_placeholder
|
|
@@ -628,10 +630,14 @@ module Sequel
|
|
|
628
630
|
BOOL_TRUE = 'true'.freeze
|
|
629
631
|
COMMA_SEPARATOR = ', '.freeze
|
|
630
632
|
DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'from using where')
|
|
633
|
+
DELETE_CLAUSE_METHODS_91 = Dataset.clause_methods(:delete, %w'with from using where returning')
|
|
631
634
|
EXCLUSIVE = 'EXCLUSIVE'.freeze
|
|
632
635
|
EXPLAIN = 'EXPLAIN '.freeze
|
|
633
636
|
EXPLAIN_ANALYZE = 'EXPLAIN ANALYZE '.freeze
|
|
634
637
|
FOR_SHARE = ' FOR SHARE'.freeze
|
|
638
|
+
INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'into columns values')
|
|
639
|
+
INSERT_CLAUSE_METHODS_82 = Dataset.clause_methods(:insert, %w'into columns values returning')
|
|
640
|
+
INSERT_CLAUSE_METHODS_91 = Dataset.clause_methods(:insert, %w'with into columns values returning')
|
|
635
641
|
LOCK = 'LOCK TABLE %s IN %s MODE'.freeze
|
|
636
642
|
NULL = LiteralString.new('NULL').freeze
|
|
637
643
|
PG_TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S".freeze
|
|
@@ -645,17 +651,23 @@ module Sequel
|
|
|
645
651
|
SHARE_UPDATE_EXCLUSIVE = 'SHARE UPDATE EXCLUSIVE'.freeze
|
|
646
652
|
SQL_WITH_RECURSIVE = "WITH RECURSIVE ".freeze
|
|
647
653
|
UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'table set from where')
|
|
654
|
+
UPDATE_CLAUSE_METHODS_91 = Dataset.clause_methods(:update, %w'with table set from where returning')
|
|
648
655
|
|
|
649
656
|
# Shared methods for prepared statements when used with PostgreSQL databases.
|
|
650
657
|
module PreparedStatementMethods
|
|
651
658
|
# Override insert action to use RETURNING if the server supports it.
|
|
659
|
+
def run(&block)
|
|
660
|
+
if @prepared_type == :insert && supports_insert_select?
|
|
661
|
+
fetch_rows(prepared_sql){|r| return r.values.first}
|
|
662
|
+
else
|
|
663
|
+
super
|
|
664
|
+
end
|
|
665
|
+
end
|
|
666
|
+
|
|
652
667
|
def prepared_sql
|
|
653
668
|
return @prepared_sql if @prepared_sql
|
|
669
|
+
@opts[:returning] = insert_pk if @prepared_type == :insert && supports_insert_select?
|
|
654
670
|
super
|
|
655
|
-
if @prepared_type == :insert and !@opts[:disable_insert_returning] and server_version >= 80200
|
|
656
|
-
@prepared_sql = insert_returning_pk_sql(*@prepared_modify_values)
|
|
657
|
-
meta_def(:insert_returning_pk_sql){|*args| prepared_sql}
|
|
658
|
-
end
|
|
659
671
|
@prepared_sql
|
|
660
672
|
end
|
|
661
673
|
end
|
|
@@ -709,25 +721,20 @@ module Sequel
|
|
|
709
721
|
end
|
|
710
722
|
|
|
711
723
|
# Insert given values into the database.
|
|
712
|
-
def insert(*values)
|
|
713
|
-
if @opts[:sql]
|
|
714
|
-
|
|
715
|
-
elsif
|
|
716
|
-
|
|
724
|
+
def insert(*values, &block)
|
|
725
|
+
if @opts[:sql] || @opts[:returning]
|
|
726
|
+
super
|
|
727
|
+
elsif supports_insert_select?
|
|
728
|
+
returning(insert_pk).insert(*values){|r| return r.values.first}
|
|
717
729
|
else
|
|
718
|
-
|
|
730
|
+
execute_insert(insert_sql(*values), :table=>opts[:from].first, :values=>values.size == 1 ? values.first : values)
|
|
719
731
|
end
|
|
720
732
|
end
|
|
721
733
|
|
|
722
|
-
# Use the RETURNING clause to return the columns listed in returning.
|
|
723
|
-
def insert_returning_sql(returning, *values)
|
|
724
|
-
"#{insert_sql(*values)} RETURNING #{column_list(Array(returning))}"
|
|
725
|
-
end
|
|
726
|
-
|
|
727
734
|
# Insert a record returning the record inserted
|
|
728
735
|
def insert_select(*values)
|
|
729
|
-
return
|
|
730
|
-
|
|
736
|
+
return unless supports_insert_select?
|
|
737
|
+
returning.insert(*values){|r| return r}
|
|
731
738
|
end
|
|
732
739
|
|
|
733
740
|
# Locks all tables in the dataset's FROM clause (but not in JOINs) with
|
|
@@ -752,6 +759,12 @@ module Sequel
|
|
|
752
759
|
[insert_sql(columns, LiteralString.new('VALUES ' + values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)))]
|
|
753
760
|
end
|
|
754
761
|
|
|
762
|
+
# PostgreSQL supports using the WITH clause in subqueries if it
|
|
763
|
+
# supports using WITH at all (i.e. on PostgreSQL 8.4+).
|
|
764
|
+
def supports_cte_in_subqueries?
|
|
765
|
+
supports_cte?
|
|
766
|
+
end
|
|
767
|
+
|
|
755
768
|
# DISTINCT ON is a PostgreSQL extension
|
|
756
769
|
def supports_distinct_on?
|
|
757
770
|
true
|
|
@@ -762,6 +775,14 @@ module Sequel
|
|
|
762
775
|
true
|
|
763
776
|
end
|
|
764
777
|
|
|
778
|
+
def supports_returning?(type)
|
|
779
|
+
if type == :insert
|
|
780
|
+
server_version >= 80200 && !opts[:disable_insert_returning]
|
|
781
|
+
else
|
|
782
|
+
server_version >= 90100
|
|
783
|
+
end
|
|
784
|
+
end
|
|
785
|
+
|
|
765
786
|
# PostgreSQL supports timezones in literal timestamps
|
|
766
787
|
def supports_timestamp_timezones?
|
|
767
788
|
true
|
|
@@ -781,7 +802,7 @@ module Sequel
|
|
|
781
802
|
|
|
782
803
|
# PostgreSQL allows deleting from joined datasets
|
|
783
804
|
def delete_clause_methods
|
|
784
|
-
DELETE_CLAUSE_METHODS
|
|
805
|
+
server_version >= 90100 ? DELETE_CLAUSE_METHODS_91 : DELETE_CLAUSE_METHODS
|
|
785
806
|
end
|
|
786
807
|
|
|
787
808
|
# Only include the primary table in the main delete clause
|
|
@@ -794,12 +815,23 @@ module Sequel
|
|
|
794
815
|
join_from_sql(:USING, sql)
|
|
795
816
|
end
|
|
796
817
|
|
|
797
|
-
#
|
|
798
|
-
def
|
|
818
|
+
# PostgreSQL allows a RETURNING clause.
|
|
819
|
+
def insert_clause_methods
|
|
820
|
+
if server_version >= 90100
|
|
821
|
+
INSERT_CLAUSE_METHODS_91
|
|
822
|
+
elsif server_version >= 80200
|
|
823
|
+
INSERT_CLAUSE_METHODS_82
|
|
824
|
+
else
|
|
825
|
+
INSERT_CLAUSE_METHODS
|
|
826
|
+
end
|
|
827
|
+
end
|
|
828
|
+
|
|
829
|
+
# Return the primary key to use for RETURNING in an INSERT statement
|
|
830
|
+
def insert_pk
|
|
799
831
|
pk = db.primary_key(opts[:from].first) if opts[:from] && !opts[:from].empty?
|
|
800
|
-
|
|
832
|
+
pk ? Sequel::SQL::Identifier.new(pk) : NULL
|
|
801
833
|
end
|
|
802
|
-
|
|
834
|
+
|
|
803
835
|
# For multiple table support, PostgreSQL requires at least
|
|
804
836
|
# two from tables, with joins allowed.
|
|
805
837
|
def join_from_sql(type, sql)
|
|
@@ -866,7 +898,7 @@ module Sequel
|
|
|
866
898
|
|
|
867
899
|
# PostgreSQL splits the main table from the joined tables
|
|
868
900
|
def update_clause_methods
|
|
869
|
-
UPDATE_CLAUSE_METHODS
|
|
901
|
+
server_version >= 90100 ? UPDATE_CLAUSE_METHODS_91 : UPDATE_CLAUSE_METHODS
|
|
870
902
|
end
|
|
871
903
|
|
|
872
904
|
# Use FROM to specify additional tables in an update query
|
|
@@ -7,9 +7,10 @@ module Sequel
|
|
|
7
7
|
AUTO_VACUUM = [:none, :full, :incremental].freeze
|
|
8
8
|
PRIMARY_KEY_INDEX_RE = /\Asqlite_autoindex_/.freeze
|
|
9
9
|
SYNCHRONOUS = [:off, :normal, :full].freeze
|
|
10
|
-
TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'"
|
|
10
|
+
TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'".freeze
|
|
11
11
|
TEMP_STORE = [:default, :file, :memory].freeze
|
|
12
|
-
|
|
12
|
+
VIEWS_FILTER = "type = 'view'".freeze
|
|
13
|
+
|
|
13
14
|
# Run all alter_table commands in a transaction. This is technically only
|
|
14
15
|
# needed for drop column.
|
|
15
16
|
def alter_table(name, generator=nil, &block)
|
|
@@ -96,11 +97,29 @@ module Sequel
|
|
|
96
97
|
end
|
|
97
98
|
end
|
|
98
99
|
|
|
100
|
+
# SQLite supports CREATE TABLE IF NOT EXISTS syntax since 3.3.0.
|
|
101
|
+
def supports_create_table_if_not_exists?
|
|
102
|
+
sqlite_version >= 30300
|
|
103
|
+
end
|
|
104
|
+
|
|
99
105
|
# SQLite 3.6.8+ supports savepoints.
|
|
100
106
|
def supports_savepoints?
|
|
101
107
|
sqlite_version >= 30608
|
|
102
108
|
end
|
|
103
109
|
|
|
110
|
+
# Override the default setting for whether to use timezones in timestamps.
|
|
111
|
+
# For backwards compatibility, it is set to +true+ by default.
|
|
112
|
+
# Anyone wanting to use SQLite's datetime functions should set it to +false+
|
|
113
|
+
# using this method. It's possible that the default will change in a future version,
|
|
114
|
+
# so anyone relying on timezones in timestamps should set this to +true+.
|
|
115
|
+
attr_writer :use_timestamp_timezones
|
|
116
|
+
|
|
117
|
+
# SQLite supports timezones in timestamps, since it just stores them as strings,
|
|
118
|
+
# but it breaks the usage of SQLite's datetime functions.
|
|
119
|
+
def use_timestamp_timezones?
|
|
120
|
+
defined?(@use_timestamp_timezones) ? @use_timestamp_timezones : (@use_timestamp_timezones = true)
|
|
121
|
+
end
|
|
122
|
+
|
|
104
123
|
# A symbol signifying the value of the synchronous PRAGMA.
|
|
105
124
|
def synchronous
|
|
106
125
|
SYNCHRONOUS[pragma_get(:synchronous).to_i]
|
|
@@ -118,8 +137,7 @@ module Sequel
|
|
|
118
137
|
# Options:
|
|
119
138
|
# * :server - Set the server to use.
|
|
120
139
|
def tables(opts={})
|
|
121
|
-
|
|
122
|
-
metadata_dataset.from(:sqlite_master).server(opts[:server]).filter(TABLES_FILTER).map{|r| m.call(r[:name])}
|
|
140
|
+
tables_and_views(TABLES_FILTER, opts)
|
|
123
141
|
end
|
|
124
142
|
|
|
125
143
|
# A symbol signifying the value of the temp_store PRAGMA.
|
|
@@ -134,6 +152,14 @@ module Sequel
|
|
|
134
152
|
pragma_set(:temp_store, value)
|
|
135
153
|
end
|
|
136
154
|
|
|
155
|
+
# Array of symbols specifying the view names in the current database.
|
|
156
|
+
#
|
|
157
|
+
# Options:
|
|
158
|
+
# * :server - Set the server to use.
|
|
159
|
+
def views(opts={})
|
|
160
|
+
tables_and_views(VIEWS_FILTER, opts)
|
|
161
|
+
end
|
|
162
|
+
|
|
137
163
|
private
|
|
138
164
|
|
|
139
165
|
# SQLite supports limited table modification. You can add a column
|
|
@@ -306,6 +332,12 @@ module Sequel
|
|
|
306
332
|
end
|
|
307
333
|
end
|
|
308
334
|
|
|
335
|
+
# Backbone of the tables and views support.
|
|
336
|
+
def tables_and_views(filter, opts)
|
|
337
|
+
m = output_identifier_meth
|
|
338
|
+
metadata_dataset.from(:sqlite_master).server(opts[:server]).filter(filter).map{|r| m.call(r[:name])}
|
|
339
|
+
end
|
|
340
|
+
|
|
309
341
|
# SQLite uses the integer data type even for bignums. This is because they
|
|
310
342
|
# are both stored internally as text, and converted when returned from
|
|
311
343
|
# the database. Using an integer type instead of bigint makes it more likely
|
|
@@ -320,7 +352,8 @@ module Sequel
|
|
|
320
352
|
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
|
|
321
353
|
COMMA_SEPARATOR = ', '.freeze
|
|
322
354
|
CONSTANT_MAP = {:CURRENT_DATE=>"date(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIMESTAMP=>"datetime(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIME=>"time(CURRENT_TIMESTAMP, 'localtime')".freeze}
|
|
323
|
-
|
|
355
|
+
EXTRACT_MAP = {:year=>"'%Y'", :month=>"'%m'", :day=>"'%d'", :hour=>"'%H'", :minute=>"'%M'", :second=>"'%f'"}
|
|
356
|
+
|
|
324
357
|
# SQLite does not support pattern matching via regular expressions.
|
|
325
358
|
# SQLite is case insensitive (depending on pragma), so use LIKE for
|
|
326
359
|
# ILIKE.
|
|
@@ -331,6 +364,15 @@ module Sequel
|
|
|
331
364
|
when :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
|
|
332
365
|
# SQLite is case insensitive for ASCII, and non case sensitive for other character sets
|
|
333
366
|
"#{'NOT ' if [:'NOT LIKE', :'NOT ILIKE'].include?(op)}(#{literal(args.at(0))} LIKE #{literal(args.at(1))})"
|
|
367
|
+
when :^
|
|
368
|
+
a = literal(args.at(0))
|
|
369
|
+
b = literal(args.at(1))
|
|
370
|
+
"((~(#{a} & #{b})) & (#{a} | #{b}))"
|
|
371
|
+
when :extract
|
|
372
|
+
part = args.at(0)
|
|
373
|
+
raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
|
|
374
|
+
expr = args.at(1)
|
|
375
|
+
"CAST(strftime(#{format}, #{literal(expr)}) AS #{part == :second ? 'NUMERIC' : 'INTEGER'})"
|
|
334
376
|
else
|
|
335
377
|
super(op, args)
|
|
336
378
|
end
|
|
@@ -382,11 +424,17 @@ module Sequel
|
|
|
382
424
|
end
|
|
383
425
|
|
|
384
426
|
# SQLite supports timezones in literal timestamps, since it stores them
|
|
385
|
-
# as text.
|
|
427
|
+
# as text. But using timezones in timestamps breaks SQLite datetime
|
|
428
|
+
# functions, so we allow the user to override the default per database.
|
|
386
429
|
def supports_timestamp_timezones?
|
|
387
|
-
|
|
430
|
+
db.use_timestamp_timezones?
|
|
388
431
|
end
|
|
389
432
|
|
|
433
|
+
# SQLite cannot use WHERE 't'.
|
|
434
|
+
def supports_where_true?
|
|
435
|
+
false
|
|
436
|
+
end
|
|
437
|
+
|
|
390
438
|
private
|
|
391
439
|
|
|
392
440
|
# SQLite uses string literals instead of identifiers in AS clauses.
|
|
@@ -406,7 +454,7 @@ module Sequel
|
|
|
406
454
|
v.each_byte{|x| blob << sprintf('%02x', x)}
|
|
407
455
|
"X'#{blob}'"
|
|
408
456
|
end
|
|
409
|
-
|
|
457
|
+
|
|
410
458
|
# SQLite does not support the SQL WITH clause
|
|
411
459
|
def select_clause_methods
|
|
412
460
|
SELECT_CLAUSE_METHODS
|
|
@@ -13,26 +13,22 @@ module Sequel
|
|
|
13
13
|
TYPE_TRANSLATOR = tt = Class.new do
|
|
14
14
|
FALSE_VALUES = %w'0 false f no n'.freeze
|
|
15
15
|
def boolean(s) !FALSE_VALUES.include?(s.downcase) end
|
|
16
|
-
def blob(s) ::Sequel::SQL::Blob.new(s) end
|
|
17
16
|
def integer(s) s.to_i end
|
|
18
17
|
def float(s) s.to_f end
|
|
19
18
|
def numeric(s) ::BigDecimal.new(s) rescue s end
|
|
20
|
-
def date(s) ::Sequel.string_to_date(s) end
|
|
21
|
-
def time(s) ::Sequel.string_to_time(s) end
|
|
22
|
-
def timestamp(s) ::Sequel.database_to_application_timestamp(s) end
|
|
23
19
|
end.new
|
|
24
20
|
|
|
25
21
|
# Hash with string keys and callable values for converting SQLite types.
|
|
26
22
|
SQLITE_TYPES = {}
|
|
27
23
|
{
|
|
28
|
-
%w'timestamp datetime' =>
|
|
29
|
-
%w'date' =>
|
|
30
|
-
%w'time' =>
|
|
24
|
+
%w'timestamp datetime' => ::Sequel.method(:database_to_application_timestamp),
|
|
25
|
+
%w'date' => ::Sequel.method(:string_to_date),
|
|
26
|
+
%w'time' => ::Sequel.method(:string_to_time),
|
|
31
27
|
%w'bit bool boolean' => tt.method(:boolean),
|
|
32
28
|
%w'integer smallint mediumint int bigint' => tt.method(:integer),
|
|
33
29
|
%w'numeric decimal money' => tt.method(:numeric),
|
|
34
30
|
%w'float double real dec fixed' + ['double precision'] => tt.method(:float),
|
|
35
|
-
%w'blob' =>
|
|
31
|
+
%w'blob' => ::Sequel::SQL::Blob.method(:new)
|
|
36
32
|
}.each do |k,v|
|
|
37
33
|
k.each{|n| SQLITE_TYPES[n] = v}
|
|
38
34
|
end
|
|
@@ -173,7 +169,7 @@ module Sequel
|
|
|
173
169
|
end
|
|
174
170
|
end
|
|
175
171
|
unless cps
|
|
176
|
-
cps = conn.prepare(sql)
|
|
172
|
+
cps = log_yield("Preparing #{name}: #{sql}"){conn.prepare(sql)}
|
|
177
173
|
conn.prepared_statements[name] = [cps, sql]
|
|
178
174
|
end
|
|
179
175
|
if block
|
|
@@ -217,7 +213,7 @@ module Sequel
|
|
|
217
213
|
# but with the keys converted to strings.
|
|
218
214
|
def map_to_prepared_args(hash)
|
|
219
215
|
args = {}
|
|
220
|
-
hash.each{|k,v| args[k.to_s] = v}
|
|
216
|
+
hash.each{|k,v| args[k.to_s.gsub('.', '__')] = v}
|
|
221
217
|
args
|
|
222
218
|
end
|
|
223
219
|
|
|
@@ -226,7 +222,12 @@ module Sequel
|
|
|
226
222
|
# SQLite uses a : before the name of the argument for named
|
|
227
223
|
# arguments.
|
|
228
224
|
def prepared_arg(k)
|
|
229
|
-
LiteralString.new("#{prepared_arg_placeholder}#{k}")
|
|
225
|
+
LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}")
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Always assume a prepared argument.
|
|
229
|
+
def prepared_arg?(k)
|
|
230
|
+
true
|
|
230
231
|
end
|
|
231
232
|
end
|
|
232
233
|
|
|
@@ -25,6 +25,15 @@ module Sequel
|
|
|
25
25
|
def schema_column_type(db_type)
|
|
26
26
|
db_type == 'tinyint(1)' ? :boolean : super
|
|
27
27
|
end
|
|
28
|
+
|
|
29
|
+
# By default, MySQL 'where id is null' selects the last inserted id.
|
|
30
|
+
# Turn that off unless explicitly enabled.
|
|
31
|
+
def setup_connection(conn)
|
|
32
|
+
super
|
|
33
|
+
sql = "SET SQL_AUTO_IS_NULL=0"
|
|
34
|
+
log_yield(sql){conn.execute(sql)} unless opts[:auto_is_null]
|
|
35
|
+
conn
|
|
36
|
+
end
|
|
28
37
|
end
|
|
29
38
|
|
|
30
39
|
# Dataset class for MySQL datasets accessed via Swift.
|
|
@@ -11,8 +11,8 @@ module Sequel
|
|
|
11
11
|
# :dataserver and :username options.
|
|
12
12
|
def connect(server)
|
|
13
13
|
opts = server_opts(server)
|
|
14
|
-
opts[:dataserver] = opts[:host]
|
|
15
14
|
opts[:username] = opts[:user]
|
|
15
|
+
set_mssql_unicode_strings
|
|
16
16
|
TinyTds::Client.new(opts)
|
|
17
17
|
end
|
|
18
18
|
|
|
@@ -32,13 +32,36 @@ module Sequel
|
|
|
32
32
|
begin
|
|
33
33
|
m = opts[:return]
|
|
34
34
|
r = nil
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
if (args = opts[:arguments]) && !args.empty?
|
|
36
|
+
types = []
|
|
37
|
+
values = []
|
|
38
|
+
args.each_with_index do |(k, v), i|
|
|
39
|
+
v, type = ps_arg_type(v)
|
|
40
|
+
types << "@#{k} #{type}"
|
|
41
|
+
values << "@#{k} = #{v}"
|
|
42
|
+
end
|
|
43
|
+
case m
|
|
44
|
+
when :do
|
|
45
|
+
sql = "#{sql}; SELECT @@ROWCOUNT AS AffectedRows"
|
|
46
|
+
single_value = true
|
|
47
|
+
when :insert
|
|
48
|
+
sql = "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
|
|
49
|
+
single_value = true
|
|
50
|
+
end
|
|
51
|
+
sql = "EXEC sp_executesql N'#{c.escape(sql)}', N'#{c.escape(types.join(', '))}', #{values.join(', ')}"
|
|
52
|
+
log_yield(sql) do
|
|
53
|
+
r = c.execute(sql)
|
|
54
|
+
r.each{|row| return row.values.first} if single_value
|
|
55
|
+
end
|
|
56
|
+
else
|
|
57
|
+
log_yield(sql) do
|
|
58
|
+
r = c.execute(sql)
|
|
59
|
+
return r.send(m) if m
|
|
60
|
+
end
|
|
38
61
|
end
|
|
39
62
|
yield(r) if block_given?
|
|
40
63
|
rescue TinyTds::Error => e
|
|
41
|
-
raise_error(e)
|
|
64
|
+
raise_error(e, :disconnect=>!c.active?)
|
|
42
65
|
ensure
|
|
43
66
|
r.cancel if r && c.sqlsent?
|
|
44
67
|
end
|
|
@@ -74,15 +97,112 @@ module Sequel
|
|
|
74
97
|
super
|
|
75
98
|
end
|
|
76
99
|
|
|
100
|
+
# tiny_tds uses TinyTds::Error as the base error class.
|
|
101
|
+
def database_error_classes
|
|
102
|
+
[TinyTds::Error]
|
|
103
|
+
end
|
|
104
|
+
|
|
77
105
|
# Close the TinyTds::Client object.
|
|
78
106
|
def disconnect_connection(c)
|
|
79
107
|
c.close
|
|
80
108
|
end
|
|
109
|
+
|
|
110
|
+
# Return true if the :conn argument is present and not active.
|
|
111
|
+
def disconnect_error?(e, opts)
|
|
112
|
+
super || (opts[:conn] && !opts[:conn].active?)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Return a 2 element array with the literal value and type to use
|
|
116
|
+
# in the prepared statement call for the given value and connection.
|
|
117
|
+
def ps_arg_type(v)
|
|
118
|
+
case v
|
|
119
|
+
when Fixnum
|
|
120
|
+
[v, 'int']
|
|
121
|
+
when Bignum
|
|
122
|
+
[v, 'bigint']
|
|
123
|
+
when Float
|
|
124
|
+
[v, 'double precision']
|
|
125
|
+
when Numeric
|
|
126
|
+
[v, 'numeric']
|
|
127
|
+
when Time
|
|
128
|
+
if v.is_a?(SQLTime)
|
|
129
|
+
[literal(v), 'time']
|
|
130
|
+
else
|
|
131
|
+
[literal(v), 'datetime']
|
|
132
|
+
end
|
|
133
|
+
when DateTime
|
|
134
|
+
[literal(v), 'datetime']
|
|
135
|
+
when Date
|
|
136
|
+
[literal(v), 'date']
|
|
137
|
+
when nil
|
|
138
|
+
['NULL', 'nvarchar(max)']
|
|
139
|
+
when true
|
|
140
|
+
['1', 'int']
|
|
141
|
+
when false
|
|
142
|
+
['0', 'int']
|
|
143
|
+
when SQL::Blob
|
|
144
|
+
[literal(v), 'varbinary(max)']
|
|
145
|
+
else
|
|
146
|
+
[literal(v), 'nvarchar(max)']
|
|
147
|
+
end
|
|
148
|
+
end
|
|
81
149
|
end
|
|
82
150
|
|
|
83
151
|
class Dataset < Sequel::Dataset
|
|
84
152
|
include Sequel::MSSQL::DatasetMethods
|
|
85
153
|
|
|
154
|
+
# SQLite already supports named bind arguments, so use directly.
|
|
155
|
+
module ArgumentMapper
|
|
156
|
+
include Sequel::Dataset::ArgumentMapper
|
|
157
|
+
|
|
158
|
+
protected
|
|
159
|
+
|
|
160
|
+
# Return a hash with the same values as the given hash,
|
|
161
|
+
# but with the keys converted to strings.
|
|
162
|
+
def map_to_prepared_args(hash)
|
|
163
|
+
args = {}
|
|
164
|
+
hash.each{|k,v| args[k.to_s.gsub('.', '__')] = v}
|
|
165
|
+
args
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
private
|
|
169
|
+
|
|
170
|
+
# SQLite uses a : before the name of the argument for named
|
|
171
|
+
# arguments.
|
|
172
|
+
def prepared_arg(k)
|
|
173
|
+
LiteralString.new("@#{k.to_s.gsub('.', '__')}")
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Always assume a prepared argument.
|
|
177
|
+
def prepared_arg?(k)
|
|
178
|
+
true
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# SQLite prepared statement uses a new prepared statement each time
|
|
183
|
+
# it is called, but it does use the bind arguments.
|
|
184
|
+
module PreparedStatementMethods
|
|
185
|
+
include ArgumentMapper
|
|
186
|
+
|
|
187
|
+
private
|
|
188
|
+
|
|
189
|
+
# Run execute_select on the database with the given SQL and the stored
|
|
190
|
+
# bind arguments.
|
|
191
|
+
def execute(sql, opts={}, &block)
|
|
192
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
|
196
|
+
def execute_dui(sql, opts={}, &block)
|
|
197
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
|
201
|
+
def execute_insert(sql, opts={}, &block)
|
|
202
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
86
206
|
# Yield hashes with symbol keys, attempting to optimize for
|
|
87
207
|
# various cases.
|
|
88
208
|
def fetch_rows(sql)
|
|
@@ -107,18 +227,30 @@ module Sequel
|
|
|
107
227
|
yield r
|
|
108
228
|
end
|
|
109
229
|
else
|
|
110
|
-
result.each(each_opts, &
|
|
230
|
+
result.each(each_opts, &Proc.new)
|
|
111
231
|
end
|
|
112
232
|
end
|
|
113
233
|
end
|
|
114
234
|
self
|
|
115
235
|
end
|
|
116
236
|
|
|
237
|
+
# Create a named prepared statement that is stored in the
|
|
238
|
+
# database (and connection) for reuse.
|
|
239
|
+
def prepare(type, name=nil, *values)
|
|
240
|
+
ps = to_prepared_statement(type, values)
|
|
241
|
+
ps.extend(PreparedStatementMethods)
|
|
242
|
+
if name
|
|
243
|
+
ps.prepared_statement_name = name
|
|
244
|
+
db.prepared_statements[name] = ps
|
|
245
|
+
end
|
|
246
|
+
ps
|
|
247
|
+
end
|
|
248
|
+
|
|
117
249
|
private
|
|
118
250
|
|
|
119
251
|
# Properly escape the given string +v+.
|
|
120
252
|
def literal_string(v)
|
|
121
|
-
db.synchronize{|c| "N'#{c.escape(v)}'"}
|
|
253
|
+
s = db.synchronize{|c| "#{'N' if mssql_unicode_strings}'#{c.escape(v)}'"}
|
|
122
254
|
end
|
|
123
255
|
end
|
|
124
256
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module EmulateOffsetWithRowNumber
|
|
3
|
+
# When a subselect that uses :offset is used in IN or NOT IN,
|
|
4
|
+
# use a nested subselect that only includes the first column
|
|
5
|
+
# instead of the ROW_NUMBER column added by the emulated offset support.
|
|
6
|
+
def complex_expression_sql(op, args)
|
|
7
|
+
case op
|
|
8
|
+
when :IN, :"NOT IN"
|
|
9
|
+
ds = args.at(1)
|
|
10
|
+
if ds.is_a?(Sequel::Dataset) && ds.opts[:offset]
|
|
11
|
+
c = ds.opts[:select].first
|
|
12
|
+
case c
|
|
13
|
+
when Symbol
|
|
14
|
+
t, cl, a = split_symbol(c)
|
|
15
|
+
if a
|
|
16
|
+
c = SQL::Identifier.new(a)
|
|
17
|
+
elsif t
|
|
18
|
+
c = SQL::Identifier.new(cl)
|
|
19
|
+
end
|
|
20
|
+
when SQL::AliasedExpression
|
|
21
|
+
c = SQL::Identifier.new(c.aliaz)
|
|
22
|
+
when SQL::QualifiedIdentifier
|
|
23
|
+
c = SQL::Identifier.new(c.column)
|
|
24
|
+
end
|
|
25
|
+
super(op, [args.at(0), ds.from_self.select(c)])
|
|
26
|
+
else
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
super
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Emulate OFFSET support with the ROW_NUMBER window function
|
|
35
|
+
#
|
|
36
|
+
# The implementation is ugly, cloning the current dataset and modifying
|
|
37
|
+
# the clone to add a ROW_NUMBER window function (and some other things),
|
|
38
|
+
# then using the modified clone in a subselect which is selected from.
|
|
39
|
+
#
|
|
40
|
+
# If offset is used, an order must be provided, because the use of ROW_NUMBER
|
|
41
|
+
# requires an order.
|
|
42
|
+
def select_sql
|
|
43
|
+
return super unless o = @opts[:offset]
|
|
44
|
+
raise(Error, "#{db.database_type} requires an order be provided if using an offset") unless order = @opts[:order]
|
|
45
|
+
dsa1 = dataset_alias(1)
|
|
46
|
+
rn = row_number_column
|
|
47
|
+
subselect_sql(unlimited.
|
|
48
|
+
unordered.
|
|
49
|
+
select_append{ROW_NUMBER(:over, :order=>order){}.as(rn)}.
|
|
50
|
+
from_self(:alias=>dsa1).
|
|
51
|
+
limit(@opts[:limit]).
|
|
52
|
+
where(SQL::Identifier.new(rn) > o))
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|