sequel 4.12.0 → 4.13.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.
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