sequel 4.12.0 → 4.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +64 -0
  3. data/Rakefile +3 -1
  4. data/bin/sequel +13 -5
  5. data/doc/release_notes/4.13.0.txt +169 -0
  6. data/doc/sql.rdoc +3 -3
  7. data/lib/sequel/adapters/do.rb +11 -23
  8. data/lib/sequel/adapters/do/mysql.rb +8 -0
  9. data/lib/sequel/adapters/do/postgres.rb +8 -0
  10. data/lib/sequel/adapters/do/{sqlite.rb → sqlite3.rb} +9 -0
  11. data/lib/sequel/adapters/jdbc.rb +16 -139
  12. data/lib/sequel/adapters/jdbc/as400.rb +9 -0
  13. data/lib/sequel/adapters/jdbc/cubrid.rb +9 -0
  14. data/lib/sequel/adapters/jdbc/db2.rb +9 -0
  15. data/lib/sequel/adapters/jdbc/derby.rb +9 -0
  16. data/lib/sequel/adapters/jdbc/{firebird.rb → firebirdsql.rb} +9 -0
  17. data/lib/sequel/adapters/jdbc/h2.rb +10 -0
  18. data/lib/sequel/adapters/jdbc/hsqldb.rb +9 -0
  19. data/lib/sequel/adapters/jdbc/{informix.rb → informix-sqli.rb} +9 -0
  20. data/lib/sequel/adapters/jdbc/{progress.rb → jdbcprogress.rb} +9 -0
  21. data/lib/sequel/adapters/jdbc/jtds.rb +10 -0
  22. data/lib/sequel/adapters/jdbc/mysql.rb +14 -0
  23. data/lib/sequel/adapters/jdbc/oracle.rb +9 -0
  24. data/lib/sequel/adapters/jdbc/postgresql.rb +9 -0
  25. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +23 -0
  26. data/lib/sequel/adapters/jdbc/sqlite.rb +10 -0
  27. data/lib/sequel/adapters/jdbc/sqlserver.rb +10 -0
  28. data/lib/sequel/adapters/odbc.rb +6 -14
  29. data/lib/sequel/adapters/odbc/db2.rb +9 -0
  30. data/lib/sequel/adapters/odbc/mssql.rb +8 -0
  31. data/lib/sequel/adapters/odbc/progress.rb +8 -0
  32. data/lib/sequel/adapters/oracle.rb +1 -1
  33. data/lib/sequel/adapters/postgres.rb +1 -1
  34. data/lib/sequel/adapters/shared/firebird.rb +8 -1
  35. data/lib/sequel/adapters/shared/mssql.rb +68 -27
  36. data/lib/sequel/adapters/shared/mysql.rb +3 -5
  37. data/lib/sequel/adapters/shared/oracle.rb +17 -3
  38. data/lib/sequel/adapters/shared/postgres.rb +9 -4
  39. data/lib/sequel/adapters/shared/sqlanywhere.rb +6 -6
  40. data/lib/sequel/database/connecting.rb +38 -17
  41. data/lib/sequel/dataset/actions.rb +6 -2
  42. data/lib/sequel/dataset/graph.rb +18 -20
  43. data/lib/sequel/dataset/misc.rb +37 -0
  44. data/lib/sequel/dataset/prepared_statements.rb +1 -2
  45. data/lib/sequel/dataset/query.rb +1 -0
  46. data/lib/sequel/dataset/sql.rb +17 -10
  47. data/lib/sequel/extensions/dataset_source_alias.rb +90 -0
  48. data/lib/sequel/extensions/pg_array.rb +14 -10
  49. data/lib/sequel/extensions/pg_enum.rb +135 -0
  50. data/lib/sequel/extensions/pg_hstore.rb +4 -6
  51. data/lib/sequel/extensions/pg_inet.rb +4 -5
  52. data/lib/sequel/extensions/pg_interval.rb +3 -3
  53. data/lib/sequel/extensions/pg_json.rb +16 -12
  54. data/lib/sequel/extensions/pg_range.rb +5 -3
  55. data/lib/sequel/extensions/pg_row.rb +2 -2
  56. data/lib/sequel/extensions/round_timestamps.rb +52 -0
  57. data/lib/sequel/model.rb +5 -2
  58. data/lib/sequel/model/associations.rb +29 -3
  59. data/lib/sequel/model/base.rb +68 -29
  60. data/lib/sequel/plugins/class_table_inheritance.rb +25 -16
  61. data/lib/sequel/plugins/column_select.rb +57 -0
  62. data/lib/sequel/plugins/composition.rb +14 -16
  63. data/lib/sequel/plugins/dirty.rb +9 -11
  64. data/lib/sequel/plugins/insert_returning_select.rb +70 -0
  65. data/lib/sequel/plugins/instance_filters.rb +7 -9
  66. data/lib/sequel/plugins/lazy_attributes.rb +16 -4
  67. data/lib/sequel/plugins/list.rb +9 -0
  68. data/lib/sequel/plugins/modification_detection.rb +90 -0
  69. data/lib/sequel/plugins/serialization.rb +13 -15
  70. data/lib/sequel/plugins/serialization_modification_detection.rb +9 -9
  71. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  72. data/lib/sequel/plugins/timestamps.rb +6 -6
  73. data/lib/sequel/version.rb +1 -1
  74. data/spec/adapters/mysql_spec.rb +7 -0
  75. data/spec/adapters/postgres_spec.rb +41 -0
  76. data/spec/bin_spec.rb +4 -1
  77. data/spec/core/database_spec.rb +6 -0
  78. data/spec/core/dataset_spec.rb +100 -90
  79. data/spec/core/object_graph_spec.rb +5 -0
  80. data/spec/extensions/class_table_inheritance_spec.rb +18 -13
  81. data/spec/extensions/column_select_spec.rb +108 -0
  82. data/spec/extensions/composition_spec.rb +20 -0
  83. data/spec/extensions/dataset_source_alias_spec.rb +51 -0
  84. data/spec/extensions/insert_returning_select_spec.rb +46 -0
  85. data/spec/extensions/lazy_attributes_spec.rb +24 -20
  86. data/spec/extensions/list_spec.rb +5 -0
  87. data/spec/extensions/modification_detection_spec.rb +80 -0
  88. data/spec/extensions/pg_enum_spec.rb +64 -0
  89. data/spec/extensions/pg_json_spec.rb +7 -13
  90. data/spec/extensions/prepared_statements_spec.rb +6 -4
  91. data/spec/extensions/round_timestamps_spec.rb +43 -0
  92. data/spec/extensions/serialization_modification_detection_spec.rb +10 -1
  93. data/spec/extensions/serialization_spec.rb +18 -0
  94. data/spec/extensions/single_table_inheritance_spec.rb +5 -0
  95. data/spec/extensions/timestamps_spec.rb +6 -0
  96. data/spec/integration/plugin_test.rb +14 -8
  97. data/spec/integration/prepared_statement_test.rb +12 -0
  98. data/spec/model/associations_spec.rb +24 -0
  99. data/spec/model/model_spec.rb +13 -3
  100. data/spec/model/record_spec.rb +24 -1
  101. metadata +22 -6
@@ -1,10 +1,10 @@
1
1
  # The pg_array extension adds support for Sequel to handle
2
2
  # PostgreSQL's array types.
3
3
  #
4
- # This extension integrates with Sequel's native postgres adapter, so
5
- # that when array fields are retrieved, they are parsed and returned
6
- # as instances of Sequel::Postgres::PGArray. PGArray is
7
- # a DelegateClass of Array, so it mostly acts like an array, but not
4
+ # This extension integrates with Sequel's native postgres adapter and
5
+ # the jdbc/postgresql adapter, so that when array fields are retrieved,
6
+ # they are parsed and returned as instances of Sequel::Postgres::PGArray.
7
+ # PGArray is a DelegateClass of Array, so it mostly acts like an array, but not
8
8
  # completely (is_a?(Array) is false). If you want the actual array,
9
9
  # you can call PGArray#to_a. This is done so that Sequel does not
10
10
  # treat a PGArray like an Array by default, which would cause issues.
@@ -39,18 +39,21 @@
39
39
  # See the {schema modification guide}[rdoc-ref:doc/schema_modification.rdoc]
40
40
  # for details on using postgres array columns in CREATE/ALTER TABLE statements.
41
41
  #
42
- # If you are not using the native postgres adapter and are using array
43
- # types as model column values you probably should use the
44
- # typecast_on_load plugin if the column values are returned as a
45
- # regular array, and the pg_typecast_on_load plugin if the column
46
- # values are returned as a string.
42
+ # If you are not using the native postgres or jdbc/postgresql adapter and are using array
43
+ # types as model column values you probably should use the the pg_typecast_on_load plugin
44
+ # if the column values are returned as a string.
47
45
  #
48
46
  # This extension by default includes handlers for array types for
49
47
  # all scalar types that the native postgres adapter handles. It
50
48
  # also makes it easy to add support for other array types. In
51
49
  # general, you just need to make sure that the scalar type is
52
50
  # handled and has the appropriate converter installed in
53
- # Sequel::Postgres::PG_TYPES under the appropriate type OID.
51
+ # Sequel::Postgres::PG_TYPES or the Database instance's
52
+ # conversion_procs usingthe appropriate type OID. For user defined
53
+ # types, you can do this via:
54
+ #
55
+ # DB.conversion_procs[scalar_type_oid] = lambda{|string| ...}
56
+ #
54
57
  # Then you can call
55
58
  # Sequel::Postgres::PGArray::DatabaseMethods#register_array_type
56
59
  # to automatically set up a handler for the array type. So if you
@@ -63,6 +66,7 @@
63
66
  # Sequel::Postgres::PGArray.register. In this case, you'll have
64
67
  # to specify the type oids:
65
68
  #
69
+ # Sequel::Postgres::PG_TYPES[1234] = lambda{|string| ...}
66
70
  # Sequel::Postgres::PGArray.register('foo', :oid=>4321, :scalar_oid=>1234)
67
71
  #
68
72
  # Both Sequel::Postgres::PGArray::DatabaseMethods#register_array_type
@@ -0,0 +1,135 @@
1
+ # The pg_enum extension adds support for Sequel to handle PostgreSQL's enum
2
+ # types. To use this extension, first load it into your Database instance:
3
+ #
4
+ # DB.extension :pg_enum
5
+ #
6
+ # It allows creation of enum types using create_enum:
7
+ #
8
+ # DB.create_enum(:type_name, %w'value1 value2 value3')
9
+ #
10
+ # You can also add values to existing enums via add_enum_value:
11
+ #
12
+ # DB.add_enum_value(:enum_type_name, 'value4')
13
+ #
14
+ # If you want to drop an enum type, you can use drop_enum:
15
+ #
16
+ # DB.drop_enum(:type_name)
17
+ #
18
+ # Just like any user-created type, after creating the type, you
19
+ # can create tables that have a column of that type:
20
+ #
21
+ # DB.create_table(:table_name)
22
+ # enum_type_name :column_name
23
+ # end
24
+ #
25
+ # When parsing the schema, enum types are recognized, and available
26
+ # values returned in the schema hash:
27
+ #
28
+ # DB.schema(:table_name)
29
+ # [[:column_name, {:type=>:enum, :enum_values=>['value1', 'value2']}]]
30
+ #
31
+ # If the pg_array extension is used, arrays of enums are returned as a
32
+ # PGArray:
33
+ #
34
+ # DB.create_table(:table_name)
35
+ # column :column_name, 'enum_type_name[]'
36
+ # end
37
+ # DB[:table_name].get(:column_name)
38
+ # # ['value1', 'value2']
39
+ #
40
+ # Finally, typecasting for enums is setup to cast to strings, which
41
+ # allows you to use symbols in your model code. Similar, you can provide
42
+ # the enum values as symbols when creating enums using create_enum or
43
+ # add_enum_value.
44
+
45
+ module Sequel
46
+ module Postgres
47
+ # Methods enabling Database object integration with enum types.
48
+ module EnumDatabaseMethods
49
+ # Parse the available enum values when loading this extension into
50
+ # your database.
51
+ def self.extended(db)
52
+ db.send(:parse_enum_labels)
53
+ end
54
+
55
+ # Run the SQL to add the given value to the existing enum type.
56
+ # Options:
57
+ # :after :: Add the new value after this existing value.
58
+ # :before :: Add the new value before this existing value.
59
+ # :if_not_exists :: Do not raise an error if the value already exists in the enum.
60
+ def add_enum_value(enum, value, opts=OPTS)
61
+ sql = "ALTER TYPE #{quote_schema_table(enum)} ADD VALUE#{' IF NOT EXISTS' if opts[:if_not_exists]} #{literal(value.to_s)}"
62
+ if v = opts[:before]
63
+ sql << " BEFORE #{literal(v.to_s)}"
64
+ elsif v = opts[:after]
65
+ sql << " AFTER #{literal(v.to_s)}"
66
+ end
67
+ run sql
68
+ parse_enum_labels
69
+ nil
70
+ end
71
+
72
+ # Run the SQL to create an enum type with the given name and values.
73
+ def create_enum(enum, values)
74
+ sql = "CREATE TYPE #{quote_schema_table(enum)} AS ENUM (#{values.map{|v| literal(v.to_s)}.join(', ')})"
75
+ run sql
76
+ parse_enum_labels
77
+ nil
78
+ end
79
+
80
+ # Run the SQL to drop the enum type with the given name.
81
+ # Options:
82
+ # :if_exists :: Do not raise an error if the enum type does not exist
83
+ # :cascade :: Also drop other objects that depend on the enum type
84
+ def drop_enum(enum, opts=OPTS)
85
+ sql = "DROP TYPE#{' IF EXISTS' if opts[:if_exists]} #{quote_schema_table(enum)}#{' CASCADE' if opts[:cascade]}"
86
+ run sql
87
+ parse_enum_labels
88
+ nil
89
+ end
90
+
91
+ private
92
+
93
+ # Parse the pg_enum table to get enum values, and
94
+ # the pg_type table to get names and array oids for
95
+ # enums.
96
+ def parse_enum_labels
97
+ @enum_labels = metadata_dataset.from(:pg_enum).
98
+ order(:enumtypid, :enumsortorder).
99
+ select_hash_groups(Sequel.cast(:enumtypid, Integer).as(:v), :enumlabel)
100
+
101
+ if respond_to?(:register_array_type)
102
+ array_types = metadata_dataset.
103
+ from(:pg_type).
104
+ where(:oid=>@enum_labels.keys).
105
+ exclude(:typarray=>0).
106
+ select_map([:typname, Sequel.cast(:typarray, Integer).as(:v)])
107
+
108
+ existing_oids = conversion_procs.keys
109
+ array_types.each do |name, oid|
110
+ next if existing_oids.include?(oid)
111
+ register_array_type(name, :oid=>oid)
112
+ end
113
+ end
114
+ end
115
+
116
+ # For schema entries that are enums, set the type to
117
+ # :enum and add a :enum_values entry with the enum values.
118
+ def schema_parse_table(*)
119
+ super.each do |_, s|
120
+ if values = @enum_labels[s[:oid]]
121
+ s[:type] = :enum
122
+ s[:enum_values] = values
123
+ end
124
+ end
125
+ end
126
+
127
+ # Typecast the given value to a string.
128
+ def typecast_value_enum(value)
129
+ value.to_s
130
+ end
131
+ end
132
+ end
133
+
134
+ Database.register_extension(:pg_enum, Postgres::EnumDatabaseMethods)
135
+ end
@@ -3,8 +3,8 @@
3
3
  # the hstore type stores an arbitrary key-value table, where the keys
4
4
  # are strings and the values are strings or NULL.
5
5
  #
6
- # This extension integrates with Sequel's native postgres adapter, so
7
- # that when hstore fields are retrieved, they are parsed and returned
6
+ # This extension integrates with Sequel's native postgres and jdbc/postgresql
7
+ # adapters, so that when hstore fields are retrieved, they are parsed and returned
8
8
  # as instances of Sequel::Postgres::HStore. HStore is
9
9
  # a DelegateClass of Hash, so it mostly acts like a hash, but not
10
10
  # completely (is_a?(Hash) is false). If you want the actual hash,
@@ -78,11 +78,9 @@
78
78
  # See the {schema modification guide}[rdoc-ref:doc/schema_modification.rdoc]
79
79
  # for details on using hstore columns in CREATE/ALTER TABLE statements.
80
80
  #
81
- # If you are not using the native postgres adapter and are using hstore
81
+ # If you are not using the native postgres or jdbc/postgresql adapters and are using hstore
82
82
  # types as model column values you probably should use the
83
- # typecast_on_load plugin if the column values are returned as a
84
- # hash, and the pg_typecast_on_load plugin if the column
85
- # values are returned as a string.
83
+ # pg_typecast_on_load plugin if the column values are returned as a string.
86
84
  #
87
85
  # This extension requires the delegate and strscan libraries.
88
86
 
@@ -1,16 +1,15 @@
1
1
  # The pg_inet extension adds support for Sequel to handle
2
2
  # PostgreSQL's inet and cidr types using ruby's IPAddr class.
3
3
  #
4
- # This extension integrates with Sequel's native postgres adapter, so
5
- # that when inet/cidr fields are retrieved, they are returned as
4
+ # This extension integrates with Sequel's native postgres and jdbc/postgresql
5
+ # adapters, so that when inet/cidr fields are retrieved, they are returned as
6
6
  # IPAddr instances
7
7
  #
8
- # After loading the extension, you should extend your dataset
9
- # with a module so that it correctly handles the inet/cidr type:
8
+ # To use this extension, load it into your database:
10
9
  #
11
10
  # DB.extension :pg_inet
12
11
  #
13
- # If you are not using the native postgres adapter and are using inet/cidr
12
+ # If you are not using the native postgres or jdbc/postgresql adapters and are using inet/cidr
14
13
  # types as model column values you probably should use the
15
14
  # pg_typecast_on_load plugin if the column values are returned as a string.
16
15
  #
@@ -1,7 +1,7 @@
1
1
  # The pg_interval extension adds support for PostgreSQL's interval type.
2
2
  #
3
- # This extension integrates with Sequel's native postgres adapter, so
4
- # that when interval type values are retrieved, they are parsed and returned
3
+ # This extension integrates with Sequel's native postgres and jdbc/postgresql
4
+ # adapters, so that when interval type values are retrieved, they are parsed and returned
5
5
  # as instances of ActiveSupport::Duration.
6
6
  #
7
7
  # In addition to the parser, this extension adds literalizers for
@@ -15,7 +15,7 @@
15
15
  #
16
16
  # DB.extension :pg_interval
17
17
  #
18
- # If you are not using the native postgres adapter and are using interval
18
+ # If you are not using the native postgres or jdbc/postgresql adapters and are using interval
19
19
  # types as model column values you probably should use the
20
20
  # pg_typecast_on_load plugin if the column values are returned as a string.
21
21
  #
@@ -1,15 +1,16 @@
1
1
  # The pg_json extension adds support for Sequel to handle
2
- # PostgreSQL's json type. It is slightly more strict than the
3
- # PostgreSQL json type in that the object returned must be an
2
+ # PostgreSQL's json and jsonb types. It is slightly more strict than the
3
+ # PostgreSQL json types in that the object returned should be an
4
4
  # array or object (PostgreSQL's json type considers plain numbers
5
- # and strings as valid). This is because Sequel relies completely
6
- # on the ruby JSON library for parsing, and ruby's JSON library
7
- # does not accept the values.
5
+ # strings, true, false, and null as valid). Sequel will work with
6
+ # PostgreSQL json values that are not arrays or objects, but support
7
+ # is fairly limited and the values do not roundtrip.
8
8
  #
9
9
  # This extension integrates with Sequel's native postgres adapter, so
10
10
  # that when json fields are retrieved, they are parsed and returned
11
11
  # as instances of Sequel::Postgres::JSONArray or
12
- # Sequel::Postgres::JSONHash. JSONArray and JSONHash are
12
+ # Sequel::Postgres::JSONHash (or JSONBArray or JSONBHash for jsonb
13
+ # columns). JSONArray and JSONHash are
13
14
  # DelegateClasses of Array and Hash, so they mostly act the same, but
14
15
  # not completely (json_array.is_a?(Array) is false). If you want
15
16
  # the actual array for a JSONArray, call JSONArray#to_a. If you want
@@ -20,15 +21,15 @@
20
21
  # To turn an existing Array or Hash into a JSONArray or JSONHash,
21
22
  # use Sequel.pg_json:
22
23
  #
23
- # Sequel.pg_json(array)
24
- # Sequel.pg_json(hash)
24
+ # Sequel.pg_json(array) # or Sequel.pg_jsonb(array) for jsonb type
25
+ # Sequel.pg_json(hash) # or Sequel.pg_jsonb(hash) for jsonb type
25
26
  #
26
27
  # If you have loaded the {core_extensions extension}[rdoc-ref:doc/core_extensions.rdoc],
27
28
  # or you have loaded the core_refinements extension
28
29
  # and have activated refinements for the file, you can also use Array#pg_json and Hash#pg_json:
29
30
  #
30
- # array.pg_json
31
- # hash.pg_json
31
+ # array.pg_json # or array.pg_jsonb for jsonb type
32
+ # hash.pg_json # or hash.pg_jsonb for jsonb type
32
33
  #
33
34
  # So if you want to insert an array or hash into an json database column:
34
35
  #
@@ -154,8 +155,9 @@ module Sequel
154
155
  end
155
156
 
156
157
  # Parse the given string as json, returning either a JSONArray
157
- # or JSONHash instance, and raising an error if the JSON
158
- # parsing does not yield an array or hash.
158
+ # or JSONHash instance (or JSONBArray or JSONBHash instance if jsonb
159
+ # argument is true), or a String, Numeric, true, false, or nil
160
+ # if the json library used supports that.
159
161
  def self.parse_json(s, jsonb=false)
160
162
  begin
161
163
  value = Sequel.parse_json(s)
@@ -168,6 +170,8 @@ module Sequel
168
170
  (jsonb ? JSONBArray : JSONArray).new(value)
169
171
  when Hash
170
172
  (jsonb ? JSONBHash : JSONHash).new(value)
173
+ when String, Numeric, true, false, nil
174
+ value
171
175
  else
172
176
  raise Sequel::InvalidValue, "unhandled json value: #{value.inspect} (from #{s.inspect})"
173
177
  end
@@ -6,7 +6,7 @@
6
6
  # unbounded beginnings and endings (which ruby's range does not
7
7
  # support).
8
8
  #
9
- # This extension integrates with Sequel's native postgres adapter, so
9
+ # This extension integrates with Sequel's native postgres and jdbc/postgresql adapters, so
10
10
  # that when range type values are retrieved, they are parsed and returned
11
11
  # as instances of Sequel::Postgres::PGRange. PGRange mostly acts
12
12
  # like a Range, but it's not a Range as not all PostgreSQL range
@@ -45,7 +45,7 @@
45
45
  #
46
46
  # DB.extension :pg_range
47
47
  #
48
- # If you are not using the native postgres adapter and are using range
48
+ # If you are not using the native postgres or jdbc/postgresql adapters and are using range
49
49
  # types as model column values you probably should use the
50
50
  # pg_typecast_on_load plugin if the column values are returned as a string.
51
51
  #
@@ -395,7 +395,9 @@ module Sequel
395
395
  end
396
396
  end
397
397
 
398
- # Whether this range is empty (has no points).
398
+ # Whether this range is empty (has no points). Note that for manually created ranges
399
+ # (ones not retrieved from the database), this will only be true if the range
400
+ # was created using the :empty option.
399
401
  def empty?
400
402
  @empty
401
403
  end
@@ -1,7 +1,7 @@
1
1
  # The pg_row extension adds support for Sequel to handle
2
2
  # PostgreSQL's row-valued/composite types.
3
3
  #
4
- # This extension integrates with Sequel's native postgres adapter, so
4
+ # This extension integrates with Sequel's native postgres and jdbc/postgresql adapters, so
5
5
  # that when composite fields are retrieved, they are parsed and returned
6
6
  # as instances of Sequel::Postgres::PGRow::(HashRow|ArrayRow), or
7
7
  # optionally a custom type. HashRow and ArrayRow are DelegateClasses of
@@ -74,7 +74,7 @@
74
74
  # DB.conversion_procs.select{|k,v| v.is_a?(Sequel::Postgres::PGRow::Parser) && \
75
75
  # v.converter && (v.converter.name.nil? || v.converter.name == '') }.map{|k,v| v}
76
76
  #
77
- # If you are not using the native postgres adapter and are using composite types
77
+ # If you are not using the native postgres or jdbc/postgresql adapters and are using composite types
78
78
  # types as model column values you probably should use the
79
79
  # pg_typecast_on_load plugin if the column values are returned as a string.
80
80
  #
@@ -0,0 +1,52 @@
1
+ # The round_timestamps extension will automatically round timestamp
2
+ # values to the database's supported level of precision before literalizing
3
+ # them.
4
+ #
5
+ # For example, if the database supports microsecond precision, and you give
6
+ # it a Time value with greater than microsecond precision, it will round it
7
+ # appropriately:
8
+ #
9
+ # Time.at(1405341161.917999982833862)
10
+ # # default: 2014-07-14 14:32:41.917999
11
+ # # with extension: 2014-07-14 14:32:41.918000
12
+ #
13
+ # The round_timestamps extension correctly deals with databases that support
14
+ # millisecond or second precision. In addition to handling Time values, it
15
+ # also handles DateTime values and Sequel::SQLTime values (for the TIME type).
16
+ #
17
+ # To round timestamps for a single dataset:
18
+ #
19
+ # ds = ds.extension(:round_timestamps)
20
+ #
21
+ # To round timestamps for all datasets on a single database:
22
+ #
23
+ # DB.extension(:round_timestamps)
24
+
25
+ unless RUBY_VERSION >= '1.9'
26
+ # :nocov:
27
+ raise LoadError, 'the round_timestamps extension only works on ruby 1.9+'
28
+ # :nocov:
29
+ end
30
+
31
+ module Sequel
32
+ class Dataset
33
+ module RoundTimestamps
34
+ # Round DateTime values before literalizing
35
+ def literal_datetime(v)
36
+ super(v + Rational(5, 10**timestamp_precision)/864000)
37
+ end
38
+
39
+ # Round Sequel::SQLTime values before literalizing
40
+ def literal_sqltime(v)
41
+ super(v.round(timestamp_precision))
42
+ end
43
+
44
+ # Round Time values before literalizing
45
+ def literal_time(v)
46
+ super(v.round(timestamp_precision))
47
+ end
48
+ end
49
+
50
+ register_extension(:round_timestamps, RoundTimestamps)
51
+ end
52
+ end
data/lib/sequel/model.rb CHANGED
@@ -35,7 +35,7 @@ module Sequel
35
35
  # dataset # => DB1[:comments]
36
36
  # end
37
37
  def self.Model(source)
38
- if cache_anonymous_models && (klass = Sequel.synchronize{Model::ANONYMOUS_MODEL_CLASSES[source]})
38
+ if cache_anonymous_models && (klass = Model::ANONYMOUS_MODEL_CLASSES_MUTEX.synchronize{Model::ANONYMOUS_MODEL_CLASSES[source]})
39
39
  return klass
40
40
  end
41
41
  klass = if source.is_a?(Database)
@@ -45,7 +45,7 @@ module Sequel
45
45
  else
46
46
  Class.new(Model).set_dataset(source)
47
47
  end
48
- Sequel.synchronize{Model::ANONYMOUS_MODEL_CLASSES[source] = klass} if cache_anonymous_models
48
+ Model::ANONYMOUS_MODEL_CLASSES_MUTEX.synchronize{Model::ANONYMOUS_MODEL_CLASSES[source] = klass} if cache_anonymous_models
49
49
  klass
50
50
  end
51
51
 
@@ -78,6 +78,9 @@ module Sequel
78
78
  # of classes when dealing with code reloading.
79
79
  ANONYMOUS_MODEL_CLASSES = {}
80
80
 
81
+ # Mutex protecting access to ANONYMOUS_MODEL_CLASSES
82
+ ANONYMOUS_MODEL_CLASSES_MUTEX = Mutex.new
83
+
81
84
  # Class methods added to model that call the method of the same name on the dataset
82
85
  DATASET_METHODS = (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS +
83
86
  [:each_server]) - [:and, :or, :[], :columns, :columns!, :delete, :update, :add_graph_aliases, :first, :first!]
@@ -1206,7 +1206,7 @@ module Sequel
1206
1206
 
1207
1207
  # The columns to select when loading the association, associated_class.table_name.* by default.
1208
1208
  def select
1209
- cached_fetch(:select){Sequel::SQL::ColumnAll.new(associated_class.table_name)}
1209
+ cached_fetch(:select){default_select}
1210
1210
  end
1211
1211
 
1212
1212
  private
@@ -1215,6 +1215,17 @@ module Sequel
1215
1215
  super.inner_join(self[:join_table], self[:right_keys].zip(right_primary_keys), :qualify=>:deep)
1216
1216
  end
1217
1217
 
1218
+ # The default selection for associations that require joins. These do not use the default
1219
+ # model selection unless all entries in the select are explicitly qualified identifiers, as
1220
+ # other it can include unqualified columns which would be made ambiguous by joining.
1221
+ def default_select
1222
+ if (sel = associated_class.dataset.opts[:select]) && sel.all?{|c| selection_is_qualified?(c)}
1223
+ sel
1224
+ else
1225
+ Sequel::SQL::ColumnAll.new(associated_class.table_name)
1226
+ end
1227
+ end
1228
+
1218
1229
  def filter_by_associations_conditions_associated_keys
1219
1230
  qualify(join_table_alias, self[:left_keys])
1220
1231
  end
@@ -1252,6 +1263,21 @@ module Sequel
1252
1263
  :many_to_many
1253
1264
  end
1254
1265
 
1266
+ # Whether the given expression represents a qualified identifier. Used to determine if it is
1267
+ # OK to use directly when joining.
1268
+ def selection_is_qualified?(c)
1269
+ case c
1270
+ when Symbol
1271
+ Sequel.split_symbol(c)[0]
1272
+ when Sequel::SQL::QualifiedIdentifier
1273
+ true
1274
+ when Sequel::SQL::AliasedExpression
1275
+ selection_is_qualified?(c.expression)
1276
+ else
1277
+ false
1278
+ end
1279
+ end
1280
+
1255
1281
  # Split the join table into source and alias parts.
1256
1282
  def split_join_table_alias
1257
1283
  associated_class.dataset.split_alias(self[:join_table])
@@ -1478,8 +1504,8 @@ module Sequel
1478
1504
  # the current association's key(s). Set to nil to not use a reciprocal.
1479
1505
  # :remover :: Proc used to define the private _remove_* method for doing the database work
1480
1506
  # to remove the association between the given object and the current object (*_to_many assocations).
1481
- # :select :: the columns to select. Defaults to the associated class's
1482
- # table_name.* in a many_to_many association, which means it doesn't include the attributes from the
1507
+ # :select :: the columns to select. Defaults to the associated class's table_name.* in an association
1508
+ # that uses joins, which means it doesn't include the attributes from the
1483
1509
  # join table. If you want to include the join table attributes, you can
1484
1510
  # use this option, but beware that the join table attributes can clash with
1485
1511
  # attributes from the model table, so you should alias any attributes that have