sequel 4.7.0 → 4.8.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +46 -0
  3. data/README.rdoc +25 -1
  4. data/doc/active_record.rdoc +1 -1
  5. data/doc/advanced_associations.rdoc +143 -17
  6. data/doc/association_basics.rdoc +80 -59
  7. data/doc/release_notes/4.8.0.txt +175 -0
  8. data/lib/sequel/adapters/odbc.rb +1 -1
  9. data/lib/sequel/adapters/odbc/mssql.rb +4 -2
  10. data/lib/sequel/adapters/shared/postgres.rb +19 -3
  11. data/lib/sequel/adapters/shared/sqlite.rb +3 -3
  12. data/lib/sequel/ast_transformer.rb +1 -1
  13. data/lib/sequel/dataset/actions.rb +1 -1
  14. data/lib/sequel/dataset/graph.rb +23 -9
  15. data/lib/sequel/dataset/misc.rb +2 -2
  16. data/lib/sequel/dataset/sql.rb +3 -3
  17. data/lib/sequel/extensions/columns_introspection.rb +1 -1
  18. data/lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb +1 -1
  19. data/lib/sequel/extensions/pg_array.rb +1 -1
  20. data/lib/sequel/extensions/pg_array_ops.rb +6 -0
  21. data/lib/sequel/extensions/pg_hstore_ops.rb +7 -0
  22. data/lib/sequel/extensions/pg_json_ops.rb +5 -0
  23. data/lib/sequel/extensions/query.rb +8 -2
  24. data/lib/sequel/extensions/to_dot.rb +1 -1
  25. data/lib/sequel/model/associations.rb +476 -152
  26. data/lib/sequel/plugins/class_table_inheritance.rb +11 -3
  27. data/lib/sequel/plugins/dataset_associations.rb +21 -18
  28. data/lib/sequel/plugins/many_through_many.rb +87 -20
  29. data/lib/sequel/plugins/nested_attributes.rb +12 -0
  30. data/lib/sequel/plugins/pg_array_associations.rb +31 -12
  31. data/lib/sequel/plugins/single_table_inheritance.rb +9 -1
  32. data/lib/sequel/sql.rb +1 -0
  33. data/lib/sequel/version.rb +1 -1
  34. data/spec/adapters/mssql_spec.rb +2 -2
  35. data/spec/adapters/postgres_spec.rb +7 -0
  36. data/spec/core/object_graph_spec.rb +250 -196
  37. data/spec/extensions/core_refinements_spec.rb +1 -1
  38. data/spec/extensions/dataset_associations_spec.rb +100 -6
  39. data/spec/extensions/many_through_many_spec.rb +1002 -19
  40. data/spec/extensions/nested_attributes_spec.rb +24 -0
  41. data/spec/extensions/pg_array_associations_spec.rb +17 -12
  42. data/spec/extensions/pg_array_spec.rb +4 -2
  43. data/spec/extensions/spec_helper.rb +1 -1
  44. data/spec/integration/associations_test.rb +1003 -48
  45. data/spec/integration/dataset_test.rb +12 -5
  46. data/spec/integration/prepared_statement_test.rb +1 -1
  47. data/spec/integration/type_test.rb +1 -1
  48. data/spec/model/associations_spec.rb +467 -130
  49. data/spec/model/eager_loading_spec.rb +332 -5
  50. metadata +5 -3
@@ -0,0 +1,175 @@
1
+ = New Features
2
+
3
+ * A one_through_one association type has been added. This is similar
4
+ to the many_to_many association type in that it uses a join table,
5
+ but it returns a single record instead of an array of records.
6
+ This is designed for cases where the foreign key in the join table
7
+ that references the current table has a unique constraint, or where
8
+ you want to use an order to just pick the first matching record.
9
+
10
+ Similarly, the many_through_many plugin now also offers a
11
+ one_through_many association.
12
+
13
+ * An association_join method has been added to model datasets, for
14
+ setting up joins based on associations. This basically does the
15
+ same join that eager_graph would do, but does not make the other
16
+ changes that eager_graph makes.
17
+
18
+ Unlike eager_graph (which uses LEFT OUTER JOINs by default),
19
+ association_join uses INNER JOINs, but there are also
20
+ association_*_join methods (e.g. association_left_join) for
21
+ using different join types.
22
+
23
+ Similar to eager_graph, you can use cascading of associations or
24
+ multiple associations.
25
+
26
+ Album.association_join(:artist, :tracks)
27
+ Artist.association_left_join(:albums=>:tracks)
28
+
29
+ * Dataset#eager_graph_with_options has been added for model
30
+ datasets. It currently supports a :join_type option, for
31
+ overriding the type of join to use on a per-call basis, as well
32
+ as a :limit_strategy option. The API is similar to eager_graph,
33
+ except that the associations to eagerly load are passed in as
34
+ a single argument, and it takes an options hash.
35
+
36
+ The :limit_strategy option works similarly to the
37
+ :eager_limit_strategy option when eagerly loading. If set to
38
+ true and the database supports window functions, it will join
39
+ the current dataset to a subquery that uses a window function
40
+ to correctly restrict the join to only those objects that fall
41
+ within the association's limit/offset.
42
+
43
+ The :limit_strategy option is not on by default. It is possible
44
+ for it to perform significantly worse than the default strategy
45
+ (which uses array slicing in ruby). The :limit_strategy
46
+ significantly changes the SQL used, and can change the results
47
+ of the query if any filters/orders related to the association
48
+ are used.
49
+
50
+ It's recommended you only use the :limit_strategy option if you
51
+ are experiencing a bottleneck and you have benchmarked that it
52
+ is faster and still produces the desired results.
53
+
54
+ Artist.eager_graph_with_options(:first_10_albums,
55
+ :limit_strategy=>true)
56
+ # SELECT artists.id, artists.name,
57
+ # first_10_albums.id AS first_10_albums_id,
58
+ # first_10_albums.name AS first_10_albums_name,
59
+ # first_10_albums.artist_id,
60
+ # first_10_albums.release_date
61
+ # FROM artists
62
+ # LEFT OUTER JOIN (
63
+ # SELECT id, name, artist_id, release_date
64
+ # FROM (
65
+ # SELECT *, row_number() OVER (PARTITION BY tracks.album_id)
66
+ # AS x_sequel_row_number_x
67
+ # FROM albums
68
+ # ) AS t1 WHERE (x_sequel_row_number_x <= 10)
69
+ # ) AS first_10_albums ON (first_10_albums.artist_id = artists.id)
70
+
71
+ * Dataset#full_text_search on PostgreSQL now supports :plain and
72
+ :phrase options. :plain takes the search terms as a single
73
+ string, and searches for rows where all terms are used.
74
+ :phrase is similar to :plain, but also adds a substring search
75
+ to ensure that the string given appears verbatim in the text.
76
+
77
+ * A :graph_order association option has been added, for using a
78
+ different order when using eager_graph. This is mostly
79
+ designed for cases where :order should be qualified in other
80
+ cases, but using a qualification breaks eager_graph because the
81
+ correct qualifier is not known until runtime.
82
+
83
+ * SQL::AliasedExpression#alias has been added as an alias for #aliaz.
84
+
85
+ = Other Improvements
86
+
87
+ * Sequel will now automatically use an eager limit strategy for
88
+ *_one associations that use an :order option. For associations
89
+ that are truly one-to-one, an :order option is not needed, so it
90
+ only makes sense to have an :order option if the association
91
+ could theoretically return multiple results (in which case an
92
+ eager limit strategy is helpful).
93
+
94
+ * The queries that Sequel uses to filter by associations when
95
+ those associations have conditions are now simpler and easier
96
+ for the database to execute.
97
+
98
+ * The queries that Sequel uses for dataset associations now handle
99
+ cases where unqualified identifiers were used in the receiving
100
+ dataset that would be made ambiguous by a join.
101
+
102
+ * A limit strategy is now used when filtering by associations if
103
+ the association has a limit and the database supports window
104
+ functions. This allows Sequel to setup a correct filter in
105
+ such cases.
106
+
107
+ Artist.where(:first_10_albums=>Album[1]).all
108
+ # SELECT *
109
+ # FROM artists
110
+ # WHERE (artists.id IN (
111
+ # SELECT albums.artist_id
112
+ # FROM albums
113
+ # WHERE ((albums.artist_id IS NOT NULL) AND (albums.id IN (
114
+ # SELECT id FROM (
115
+ # SELECT albums.id, row_number() OVER
116
+ # (PARTITION BY albums.artist_id ORDER BY release_date)
117
+ # AS x_sequel_row_number_x
118
+ # FROM albums
119
+ # ) AS t1
120
+ # WHERE (x_sequel_row_number_x <= 10)
121
+ # )) AND (albums.id = 1))))
122
+
123
+ * A limit strategy is now used in the dataset_associations plugin
124
+ if the association has a limit and the database supports window
125
+ functions. This makes the resulting datasets return correct
126
+ results.
127
+
128
+ Artist.first_10_albums
129
+ # SELECT *
130
+ # FROM albums
131
+ # WHERE ((albums.artist_id IN (
132
+ # SELECT artists.id FROM artists)
133
+ # ) AND (albums.id IN (
134
+ # SELECT id FROM (
135
+ # SELECT albums.id, row_number() OVER
136
+ # (PARTITION BY albums.artist_id ORDER BY release_date)
137
+ # AS x_sequel_row_number_x
138
+ # FROM albums
139
+ # ) AS t1
140
+ # WHERE (x_sequel_row_number_x <= 10)
141
+ # )))
142
+ # ORDER BY release_date
143
+
144
+ * You can now pass symbols with embedded qualifiers or aliases,
145
+ as well as SQL::Identifier, SQL::QualifiedIdentifier, and
146
+ SQL::AliasedExpression objects as the first argument to
147
+ Dataset#graph.
148
+
149
+ * The nested_attributes plugin now automatically handles presence
150
+ validations on foreign keys when creating associated objects.
151
+ It now sets the foreign key value (or a placeholder value)
152
+ before validating such objects.
153
+
154
+ * Offsets on *_one associations are now respected when using
155
+ eager_graph.
156
+
157
+ * eager graphing *_many associations with offsets no longer breaks
158
+ if there are no associated results.
159
+
160
+ * Database#register_array_type in the pg_array extension now works
161
+ correctly if there is no existing scalar conversion proc for
162
+ the type.
163
+
164
+ * Unique, foreign key, and not null constraint violations are now
165
+ recognized correctly on SQLite 3.8.2+.
166
+
167
+ * The odbc adapter now returns fractional seconds in timestamps.
168
+
169
+ * The obdc/mssql adapter now inputs timestamps with 3 decimal
170
+ places.
171
+
172
+ = Backwards Compatibility
173
+
174
+ * The private Model.apply_window_function_eager_limit_strategy
175
+ method has been removed.
@@ -127,7 +127,7 @@ module Sequel
127
127
  # ODBCColumn#mapSqlTypeToGenericType and Column#klass.
128
128
  case v
129
129
  when ::ODBC::TimeStamp
130
- db.to_application_timestamp([v.year, v.month, v.day, v.hour, v.minute, v.second])
130
+ db.to_application_timestamp([v.year, v.month, v.day, v.hour, v.minute, v.second, v.fraction])
131
131
  when ::ODBC::Time
132
132
  Sequel::SQLTime.create(v.hour, v.minute, v.second)
133
133
  when ::ODBC::Date
@@ -32,10 +32,12 @@ module Sequel
32
32
  class Dataset < ODBC::Dataset
33
33
  include Sequel::MSSQL::DatasetMethods
34
34
 
35
+ # Use ODBC format, not Microsoft format, as the ODBC layer does
36
+ # some translation. MSSQL version is over-ridden to allow 3 millisecond decimal places
37
+ TIMESTAMP_FORMAT="{ts '%Y-%m-%d %H:%M:%S%N'}".freeze
38
+
35
39
  private
36
40
 
37
- # Use ODBC format, not Microsoft format, as the ODBC layer does
38
- # some translation.
39
41
  def default_timestamp_format
40
42
  TIMESTAMP_FORMAT
41
43
  end
@@ -1193,12 +1193,28 @@ module Sequel
1193
1193
  lock_style(:share)
1194
1194
  end
1195
1195
 
1196
- # PostgreSQL specific full text search syntax, using tsearch2 (included
1197
- # in 8.3 by default, and available for earlier versions as an add-on).
1196
+ # Run a full text search on PostgreSQL. By default, searching for the inclusion
1197
+ # of any of the terms in any of the cols.
1198
+ #
1199
+ # Options:
1200
+ # :language :: The language to use for the search (default: 'simple')
1201
+ # :plain :: Whether a plain search should be used (default: false). In this case,
1202
+ # terms should be a single string, and it will do a search where cols
1203
+ # contains all of the words in terms. This ignores search operators in terms.
1204
+ # :phrase :: Similar to :plain, but also adding an ILIKE filter to ensure that
1205
+ # returned rows also include the exact phrase used.
1198
1206
  def full_text_search(cols, terms, opts = OPTS)
1199
1207
  lang = opts[:language] || 'simple'
1200
1208
  terms = terms.join(' | ') if terms.is_a?(Array)
1201
- filter("to_tsvector(?::regconfig, ?) @@ to_tsquery(?::regconfig, ?)", lang, full_text_string_join(cols), lang, terms)
1209
+ to_tsquery = (opts[:phrase] || opts[:plain]) ? 'plainto_tsquery' : 'to_tsquery'
1210
+
1211
+ ds = where(Sequel.lit(["(to_tsvector(", "::regconfig, ", ") @@ #{to_tsquery}(", "::regconfig, ", "))"], lang, full_text_string_join(cols), lang, terms))
1212
+
1213
+ if opts[:phrase]
1214
+ ds = ds.grep(cols, "%#{escape_like(terms)}%", :case_insensitive=>true)
1215
+ end
1216
+
1217
+ ds
1202
1218
  end
1203
1219
 
1204
1220
  # Insert given values into the database.
@@ -332,10 +332,10 @@ module Sequel
332
332
  end
333
333
 
334
334
  DATABASE_ERROR_REGEXPS = {
335
- /(is|are) not unique\z/ => UniqueConstraintViolation,
336
- /foreign key constraint failed\z/ => ForeignKeyConstraintViolation,
335
+ /(is|are) not unique\z|UNIQUE constraint failed: .+\z/ => UniqueConstraintViolation,
336
+ /foreign key constraint failed\z|FOREIGN KEY constraint failed\z/ => ForeignKeyConstraintViolation,
337
337
  /\A(SQLITE ERROR 19 \(CONSTRAINT\) : )?constraint failed\z/ => ConstraintViolation,
338
- /may not be NULL\z/ => NotNullConstraintViolation,
338
+ /may not be NULL\z|NOT NULL constraint failed: .+\z/ => NotNullConstraintViolation,
339
339
  }.freeze
340
340
  def database_error_regexps
341
341
  DATABASE_ERROR_REGEXPS
@@ -33,7 +33,7 @@ module Sequel
33
33
  when SQL::OrderedExpression
34
34
  SQL::OrderedExpression.new(v(o.expression), o.descending, :nulls=>o.nulls)
35
35
  when SQL::AliasedExpression
36
- SQL::AliasedExpression.new(v(o.expression), o.aliaz)
36
+ SQL::AliasedExpression.new(v(o.expression), o.alias)
37
37
  when SQL::CaseExpression
38
38
  args = [v(o.conditions), v(o.default)]
39
39
  args << v(o.expression) if o.expression?
@@ -825,7 +825,7 @@ module Sequel
825
825
  when SQL::QualifiedIdentifier
826
826
  _hash_key_symbol(s.column, true)
827
827
  when SQL::AliasedExpression
828
- _hash_key_symbol(s.aliaz, true)
828
+ _hash_key_symbol(s.alias, true)
829
829
  when String
830
830
  s.to_sym if recursing
831
831
  end
@@ -26,7 +26,7 @@ module Sequel
26
26
  #
27
27
  # Arguments:
28
28
  # dataset :: Can be a symbol (specifying a table), another dataset,
29
- # or an object that responds to +dataset+ and returns a symbol or a dataset
29
+ # or an SQL::Identifier, SQL::QualifiedIdentifier, or SQL::AliasedExpression.
30
30
  # join_conditions :: Any condition(s) allowed by +join_table+.
31
31
  # block :: A block that is passed to +join_table+.
32
32
  #
@@ -50,22 +50,36 @@ module Sequel
50
50
  # Allow the use of a dataset or symbol as the first argument
51
51
  # Find the table name/dataset based on the argument
52
52
  table_alias = options[:table_alias]
53
+ table = dataset
54
+ create_dataset = true
55
+
53
56
  case dataset
54
57
  when Symbol
55
- table = dataset
56
- dataset = @db[dataset]
57
- table_alias ||= table
58
- when ::Sequel::Dataset
58
+ # let alias be the same as the table name (sans any optional schema)
59
+ # unless alias explicitly given in the symbol using ___ notation
60
+ table_alias ||= split_symbol(table).compact.last
61
+ when Dataset
59
62
  if dataset.simple_select_all?
60
63
  table = dataset.opts[:from].first
61
64
  table_alias ||= table
62
65
  else
63
- table = dataset
64
66
  table_alias ||= dataset_alias((@opts[:num_dataset_sources] || 0)+1)
65
67
  end
68
+ create_dataset = false
69
+ when SQL::Identifier
70
+ table_alias ||= table.value
71
+ when SQL::QualifiedIdentifier
72
+ table_alias ||= split_qualifiers(table).last
73
+ when SQL::AliasedExpression
74
+ return graph(table.expression, join_conditions, {:table_alias=>table.alias}.merge(options), &block)
66
75
  else
67
76
  raise Error, "The dataset argument should be a symbol or dataset"
68
77
  end
78
+ table_alias = table_alias.to_sym
79
+
80
+ if create_dataset
81
+ dataset = db.from(table)
82
+ end
69
83
 
70
84
  # Raise Sequel::Error with explanation that the table alias has been used
71
85
  raise_alias_error = lambda do
@@ -76,8 +90,8 @@ module Sequel
76
90
  # Only allow table aliases that haven't been used
77
91
  raise_alias_error.call if @opts[:graph] && @opts[:graph][:table_aliases] && @opts[:graph][:table_aliases].include?(table_alias)
78
92
 
79
- # Use a from_self if this is already a joined table
80
- ds = (!@opts[:graph] && (@opts[:from].length > 1 || @opts[:join])) ? from_self(:alias=>options[:from_self_alias] || first_source) : self
93
+ # Use a from_self if this is already a joined table (or from_self specifically disabled for graphs)
94
+ ds = (@opts[:graph_from_self] != false && !@opts[:graph] && (@opts[:from].length > 1 || @opts[:join])) ? from_self(:alias=>options[:from_self_alias] || first_source) : self
81
95
 
82
96
  # Join the table early in order to avoid cloning the dataset twice
83
97
  ds = ds.join_table(options[:join_type] || :left_outer, table, join_conditions, :table_alias=>table_alias, :implicit_qualifier=>options[:implicit_qualifier], :qualify=>options[:qualify], &block)
@@ -121,7 +135,7 @@ module Sequel
121
135
  column = column.value if column.is_a?(SQL::Identifier)
122
136
  column.to_sym
123
137
  when SQL::AliasedExpression
124
- column = sel.aliaz
138
+ column = sel.alias
125
139
  column = column.value if column.is_a?(SQL::Identifier)
126
140
  column.to_sym
127
141
  else
@@ -97,7 +97,7 @@ module Sequel
97
97
  end
98
98
  case s = source.first
99
99
  when SQL::AliasedExpression
100
- s.aliaz
100
+ s.alias
101
101
  when Symbol
102
102
  _, _, aliaz = split_symbol(s)
103
103
  aliaz ? aliaz.to_sym : s
@@ -178,7 +178,7 @@ module Sequel
178
178
  c_table, column, aliaz = split_symbol(c)
179
179
  [c_table ? SQL::QualifiedIdentifier.new(c_table, column.to_sym) : column.to_sym, aliaz]
180
180
  when SQL::AliasedExpression
181
- [c.expression, c.aliaz]
181
+ [c.expression, c.alias]
182
182
  when SQL::JoinClause
183
183
  [c.table, c.table_alias]
184
184
  else
@@ -298,7 +298,7 @@ module Sequel
298
298
  # SQL fragment for AliasedExpression
299
299
  def aliased_expression_sql_append(sql, ae)
300
300
  literal_append(sql, ae.expression)
301
- as_sql_append(sql, ae.aliaz)
301
+ as_sql_append(sql, ae.alias)
302
302
  end
303
303
 
304
304
  # SQL fragment for Array
@@ -801,7 +801,7 @@ module Sequel
801
801
  when SQL::QualifiedIdentifier
802
802
  alias_symbol(sym.column)
803
803
  when SQL::AliasedExpression
804
- alias_alias_symbol(sym.aliaz)
804
+ alias_alias_symbol(sym.alias)
805
805
  else
806
806
  raise Error, "Invalid alias for alias_symbol: #{sym.inspect}"
807
807
  end
@@ -1183,7 +1183,7 @@ module Sequel
1183
1183
  schema, table, t_alias = split_symbol(table)
1184
1184
  t_alias ||= Sequel::SQL::QualifiedIdentifier.new(schema, table) if schema
1185
1185
  when Sequel::SQL::AliasedExpression
1186
- t_alias = table.aliaz
1186
+ t_alias = table.alias
1187
1187
  end
1188
1188
  c_table = t_alias || table
1189
1189
  end
@@ -71,7 +71,7 @@ module Sequel
71
71
  col = c.column
72
72
  col.is_a?(SQL::Identifier) ? col.value.to_sym : col.to_sym
73
73
  when SQL::AliasedExpression
74
- a = c.aliaz
74
+ a = c.alias
75
75
  a.is_a?(SQL::Identifier) ? a.value.to_sym : a.to_sym
76
76
  end
77
77
  end
@@ -58,7 +58,7 @@ module Sequel
58
58
  ds = from(*source)
59
59
  lateral.each do |l|
60
60
  l = if l.is_a?(Sequel::SQL::AliasedExpression)
61
- l.expression.clone(:lateral=>nil).as(l.aliaz)
61
+ l.expression.clone(:lateral=>nil).as(l.alias)
62
62
  else
63
63
  l.clone(:lateral=>nil)
64
64
  end
@@ -185,7 +185,7 @@ module Sequel
185
185
 
186
186
  if soid = opts[:scalar_oid]
187
187
  raise Error, "can't provide both a converter and :scalar_oid option to register" if converter
188
- raise Error, "no conversion proc for :scalar_oid=>#{soid.inspect}" unless converter = type_procs[soid]
188
+ converter = type_procs[soid]
189
189
  end
190
190
 
191
191
  array_type = (opts[:array_type] || db_type).to_s.dup.freeze
@@ -42,6 +42,8 @@
42
42
  # ia.any # ANY(int_array_column)
43
43
  # ia.all # ALL(int_array_column)
44
44
  # ia.dims # array_dims(int_array_column)
45
+ # ia.hstore # hstore(int_array_column)
46
+ # ia.hstore(:a) # hstore(int_array_column, a)
45
47
  # ia.length # array_length(int_array_column, 1)
46
48
  # ia.length(2) # array_length(int_array_column, 2)
47
49
  # ia.lower # array_lower(int_array_column, 1)
@@ -57,6 +59,10 @@
57
59
  # If you are also using the pg_array extension, you should load it before
58
60
  # loading this extension. Doing so will allow you to use PGArray#op to get
59
61
  # an ArrayOp, allowing you to perform array operations on array literals.
62
+ #
63
+ # In order for #hstore to automatically wrap the returned value correctly in
64
+ # an HStoreOp, you need to load the pg_hstore_ops extension.
65
+
60
66
  module Sequel
61
67
  module Postgres
62
68
  # The ArrayOp class is a simple container for a single object that
@@ -58,6 +58,13 @@
58
58
  # If you are also using the pg_hstore extension, you should load it before
59
59
  # loading this extension. Doing so will allow you to use HStore#op to get
60
60
  # an HStoreOp, allowing you to perform hstore operations on hstore literals.
61
+ #
62
+ # Some of these methods will accept ruby arrays and convert them automatically to
63
+ # PostgreSQL arrays if you have the pg_array extension loaded. Some of these methods
64
+ # will accept ruby hashes and convert them automatically to PostgreSQL hstores if the
65
+ # pg_hstore extension is loaded. Methods representing expressions that return
66
+ # PostgreSQL arrays will have the returned expression automatically wrapped in a
67
+ # Postgres::ArrayOp if the pg_array_ops extension is loaded.
61
68
 
62
69
  module Sequel
63
70
  module Postgres
@@ -49,6 +49,11 @@
49
49
  # loading this extension. Doing so will allow you to use JSONHash#op and
50
50
  # JSONArray#op to get a JSONOp, allowing you to perform json operations
51
51
  # on json literals.
52
+ #
53
+ # In order to get the automatic conversion from a ruby array to a PostgreSQL array
54
+ # (as shown in the #[] and #get_text examples above), you need to load the pg_array
55
+ # extension.
56
+
52
57
  module Sequel
53
58
  module Postgres
54
59
  # The JSONOp class is a simple container for a single object that