sequel_core 1.0.8.2 → 1.0.9.1

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 CHANGED
@@ -1,3 +1,21 @@
1
+ === SVN
2
+
3
+ * Changed Dataset#all to accept opts and iteration block.
4
+
5
+ === 1.0.9 (2008-02-10)
6
+
7
+ * Implemented Dataset#inspect and Database#inspect (#151).
8
+
9
+ * Added full-text searching for odbc_mssql adapter (thanks Joseph Love).
10
+
11
+ * Added AlterTableGenerator#add_full_text_index method.
12
+
13
+ * Implemented full_text indexing and searching for PostgreSQL adapter (thanks David Lee).
14
+
15
+ * Implemented full_text indexing and searching for MySQL adapter (thanks David Lee).
16
+
17
+ * Fixed Dataset#insert_sql to work with array subscript references (thanks Jim Morris).
18
+
1
19
  === 1.0.8 (2008-02-08)
2
20
 
3
21
  * Added support for multiple choices in string matching expressions (#147).
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ include FileUtils
9
9
  # Configuration
10
10
  ##############################################################################
11
11
  NAME = "sequel_core"
12
- VERS = "1.0.8.2"
12
+ VERS = "1.0.9.1"
13
13
  CLEAN.include ["**/.*.sw?", "pkg/*", ".config", "doc/*", "coverage/*"]
14
14
  RDOC_OPTS = [
15
15
  "--quiet",
@@ -173,6 +173,17 @@ module Sequel
173
173
  sql
174
174
  end
175
175
 
176
+ def index_definition_sql(table_name, index)
177
+ index_name = index[:name] || default_index_name(table_name, index[:columns])
178
+ if index[:full_text]
179
+ "CREATE FULLTEXT INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
180
+ elsif index[:unique]
181
+ "CREATE UNIQUE INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
182
+ else
183
+ "CREATE INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
184
+ end
185
+ end
186
+
176
187
  def transaction
177
188
  @pool.hold do |conn|
178
189
  @transactions ||= []
@@ -290,6 +301,11 @@ module Sequel
290
301
  sql
291
302
  end
292
303
  alias_method :sql, :select_sql
304
+
305
+ def full_text_search(cols, terms, opts = {})
306
+ mode = opts[:boolean] ? " IN BOOLEAN MODE" : ""
307
+ filter("MATCH (#{literal(cols)}) AGAINST (#{literal(terms)}#{mode})")
308
+ end
293
309
 
294
310
  # MySQL allows HAVING clause on ungrouped datasets.
295
311
  def having(*cond, &block)
@@ -87,6 +87,10 @@ module Sequel
87
87
  sql
88
88
  end
89
89
  alias_method :sql, :select_sql
90
+
91
+ def full_text_search(cols, terms, opts = {})
92
+ filter("CONTAINS (#{literal(cols)}, #{literal(terms)})")
93
+ end
90
94
  end
91
95
  end
92
96
  end
@@ -311,6 +311,20 @@ module Sequel
311
311
  {:primary_key => true, :type => :serial}
312
312
  end
313
313
 
314
+ def index_definition_sql(table_name, index)
315
+ index_name = index[:name] || default_index_name(table_name, index[:columns])
316
+ if index[:full_text]
317
+ lang = index[:language] ? "#{literal(index[:language])}, " : ""
318
+ cols = index[:columns].map {|c| literal(c)}.join(" || ")
319
+ expr = "gin(to_tsvector(#{lang}#{cols}))"
320
+ "CREATE INDEX #{index_name} ON #{table_name} USING #{expr}"
321
+ elsif index[:unique]
322
+ "CREATE UNIQUE INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
323
+ else
324
+ "CREATE INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
325
+ end
326
+ end
327
+
314
328
  def drop_table_sql(name)
315
329
  "DROP TABLE #{name} CASCADE"
316
330
  end
@@ -341,6 +355,13 @@ module Sequel
341
355
  end
342
356
  end
343
357
 
358
+ def full_text_search(cols, terms, opts = {})
359
+ lang = opts[:language] ? "#{literal(opts[:language])}, " : ""
360
+ cols = cols.is_a?(Array) ? cols.map {|c| literal(c)}.join(" || ") : literal(cols)
361
+ terms = terms.is_a?(Array) ? literal(terms.join(" | ")) : literal(terms)
362
+ filter("to_tsvector(#{lang}#{cols}) @@ to_tsquery(#{lang}#{terms})")
363
+ end
364
+
344
365
  FOR_UPDATE = ' FOR UPDATE'.freeze
345
366
  FOR_SHARE = ' FOR SHARE'.freeze
346
367
 
@@ -337,6 +337,12 @@ module Sequel
337
337
  end
338
338
  end
339
339
  end
340
+
341
+ # Returns a string representation of the database object including the
342
+ # class name and the connection URI.
343
+ def inspect
344
+ '#<%s: %s>' % [self.class.to_s, uri.inspect]
345
+ end
340
346
 
341
347
  @@adapters = Hash.new
342
348
 
@@ -72,8 +72,16 @@ module Sequel
72
72
  attr_reader :db
73
73
  attr_accessor :opts
74
74
 
75
- alias_method :all, :to_a
76
75
  alias_method :size, :count
76
+
77
+ # Returns an array with all records in the dataset. If a block is given,
78
+ # the array is iterated over.
79
+ def all(opts = nil, &block)
80
+ a = []
81
+ each(opts) {|r| a << r}
82
+ a.each(&block) if block
83
+ a
84
+ end
77
85
 
78
86
  # Constructs a new instance of a dataset with a database instance, initial
79
87
  # options and an optional record class. Datasets are usually constructed by
@@ -421,6 +429,12 @@ module Sequel
421
429
  def self.inherited(c) #:nodoc:
422
430
  @@dataset_classes << c
423
431
  end
432
+
433
+ # Returns a string representation of the dataset including the class name
434
+ # and the corresponding SQL select statement.
435
+ def inspect
436
+ '#<%s: %s>' % [self.class.to_s, sql.inspect]
437
+ end
424
438
  end
425
439
  end
426
440
 
@@ -496,7 +496,7 @@ module Sequel
496
496
  if values.empty?
497
497
  "INSERT INTO #{@opts[:from]} DEFAULT VALUES"
498
498
  elsif values.keys
499
- fl = values.keys.map {|f| literal(f.to_sym)}
499
+ fl = values.keys.map {|f| literal(f.is_a?(String) ? f.to_sym : f)}
500
500
  vl = @transform ? transform_save(values.values) : values.values
501
501
  vl.map! {|v| literal(v)}
502
502
  "INSERT INTO #{@opts[:from]} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
@@ -509,7 +509,7 @@ module Sequel
509
509
  "INSERT INTO #{@opts[:from]} DEFAULT VALUES"
510
510
  else
511
511
  fl, vl = [], []
512
- values.each {|k, v| fl << literal(k.to_sym); vl << literal(v)}
512
+ values.each {|k, v| fl << literal(k.is_a?(String) ? k.to_sym : k); vl << literal(v)}
513
513
  "INSERT INTO #{@opts[:from]} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
514
514
  end
515
515
  when Dataset
@@ -58,6 +58,11 @@ module Sequel
58
58
  @indexes << {:columns => columns}.merge(opts)
59
59
  end
60
60
 
61
+ def full_text_index(columns, opts = {})
62
+ columns = [columns] unless columns.is_a?(Array)
63
+ @indexes << {:columns => columns, :full_text => true}.merge(opts)
64
+ end
65
+
61
66
  def unique(columns, opts = {})
62
67
  index(columns, {:unique => true}.merge(opts))
63
68
  end
@@ -126,6 +131,15 @@ module Sequel
126
131
  }.merge(opts)
127
132
  end
128
133
 
134
+ def add_full_text_index(columns, opts = {})
135
+ columns = [columns] unless columns.is_a?(Array)
136
+ @operations << { \
137
+ :op => :add_index, \
138
+ :columns => columns, \
139
+ :full_text => true \
140
+ }.merge(opts)
141
+ end
142
+
129
143
  def drop_index(columns)
130
144
  columns = [columns] unless columns.is_a?(Array)
131
145
  @operations << { \
@@ -90,7 +90,9 @@ module Sequel
90
90
 
91
91
  def index_definition_sql(table_name, index)
92
92
  index_name = index[:name] || default_index_name(table_name, index[:columns])
93
- if index[:unique]
93
+ if index[:full_text]
94
+ raise Error, "Full-text indexes are not supported for this database"
95
+ elsif index[:unique]
94
96
  "CREATE UNIQUE INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
95
97
  else
96
98
  "CREATE INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
@@ -413,3 +413,31 @@ context "A grouped MySQL dataset" do
413
413
  ds.count.should == 1
414
414
  end
415
415
  end
416
+
417
+ context "A MySQL database" do
418
+ setup do
419
+ end
420
+
421
+ specify "should support fulltext indexes" do
422
+ g = Sequel::Schema::Generator.new(MYSQL_DB) do
423
+ text :title
424
+ text :body
425
+ full_text_index [:title, :body]
426
+ end
427
+ MYSQL_DB.create_table_sql_list(:posts, *g.create_info).should == [
428
+ "CREATE TABLE posts (`title` text, `body` text)",
429
+ "CREATE FULLTEXT INDEX posts_title_body_index ON posts (`title`, `body`)"
430
+ ]
431
+ end
432
+
433
+ specify "should support full_text_search" do
434
+ MYSQL_DB[:posts].full_text_search(:title, 'ruby').sql.should ==
435
+ "SELECT * FROM posts WHERE (MATCH (`title`) AGAINST ('ruby'))"
436
+
437
+ MYSQL_DB[:posts].full_text_search([:title, :body], ['ruby', 'sequel']).sql.should ==
438
+ "SELECT * FROM posts WHERE (MATCH (`title`, `body`) AGAINST ('ruby', 'sequel'))"
439
+
440
+ MYSQL_DB[:posts].full_text_search(:title, '+ruby -rails', :boolean => true).sql.should ==
441
+ "SELECT * FROM posts WHERE (MATCH (`title`) AGAINST ('+ruby -rails' IN BOOLEAN MODE))"
442
+ end
443
+ end
@@ -245,3 +245,43 @@ context "A PostgreSQL database" do
245
245
  @db[:test2].first[:xyz].should == 57
246
246
  end
247
247
  end
248
+
249
+ context "A PostgreSSQL database" do
250
+ setup do
251
+ end
252
+
253
+ specify "should support fulltext indexes" do
254
+ g = Sequel::Schema::Generator.new(PGSQL_DB) do
255
+ text :title
256
+ text :body
257
+ full_text_index [:title, :body]
258
+ end
259
+ PGSQL_DB.create_table_sql_list(:posts, *g.create_info).should == [
260
+ "CREATE TABLE posts (\"title\" text, \"body\" text)",
261
+ "CREATE INDEX posts_title_body_index ON posts USING gin(to_tsvector(\"title\" || \"body\"))"
262
+ ]
263
+ end
264
+
265
+ specify "should support fulltext indexes with a specific language" do
266
+ g = Sequel::Schema::Generator.new(PGSQL_DB) do
267
+ text :title
268
+ text :body
269
+ full_text_index [:title, :body], :language => 'french'
270
+ end
271
+ PGSQL_DB.create_table_sql_list(:posts, *g.create_info).should == [
272
+ "CREATE TABLE posts (\"title\" text, \"body\" text)",
273
+ "CREATE INDEX posts_title_body_index ON posts USING gin(to_tsvector('french', \"title\" || \"body\"))"
274
+ ]
275
+ end
276
+
277
+ specify "should support full_text_search" do
278
+ PGSQL_DB[:posts].full_text_search(:title, 'ruby').sql.should ==
279
+ "SELECT * FROM posts WHERE (to_tsvector(\"title\") @@ to_tsquery('ruby'))"
280
+
281
+ PGSQL_DB[:posts].full_text_search([:title, :body], ['ruby', 'sequel']).sql.should ==
282
+ "SELECT * FROM posts WHERE (to_tsvector(\"title\" || \"body\") @@ to_tsquery('ruby | sequel'))"
283
+
284
+ PGSQL_DB[:posts].full_text_search(:title, 'ruby', :language => 'french').sql.should ==
285
+ "SELECT * FROM posts WHERE (to_tsvector('french', \"title\") @@ to_tsquery('french', 'ruby'))"
286
+ end
287
+ end
@@ -835,3 +835,15 @@ context "Database.connect" do
835
835
  db.opts[:host].should == 'alfonso'
836
836
  end
837
837
  end
838
+
839
+ context "Database#inspect" do
840
+ setup do
841
+ @db = DummyDatabase.new
842
+
843
+ @db.meta_def(:uri) {'blah://blahblah/blah'}
844
+ end
845
+
846
+ specify "should include the class name and the connection url" do
847
+ @db.inspect.should == '#<DummyDatabase: "blah://blahblah/blah">'
848
+ end
849
+ end
data/spec/dataset_spec.rb CHANGED
@@ -18,21 +18,21 @@ context "Dataset" do
18
18
  d.opts.should == {}
19
19
  end
20
20
 
21
- specify "should provide clone for chainability." do
22
- d1 = @dataset.clone(:from => :test)
21
+ specify "should provide clone for chainability" do
22
+ d1 = @dataset.clone(:from => [:test])
23
23
  d1.class.should == @dataset.class
24
24
  d1.should_not == @dataset
25
25
  d1.db.should be(@dataset.db)
26
- d1.opts[:from].should == :test
26
+ d1.opts[:from].should == [:test]
27
27
  @dataset.opts[:from].should be_nil
28
28
 
29
- d2 = d1.clone(:order => :name)
29
+ d2 = d1.clone(:order => [:name])
30
30
  d2.class.should == @dataset.class
31
31
  d2.should_not == d1
32
32
  d2.should_not == @dataset
33
33
  d2.db.should be(@dataset.db)
34
- d2.opts[:from].should == :test
35
- d2.opts[:order].should == :name
34
+ d2.opts[:from].should == [:test]
35
+ d2.opts[:order].should == [:name]
36
36
  d1.opts[:order].should be_nil
37
37
  end
38
38
 
@@ -2539,6 +2539,24 @@ context "Dataset#update_sql" do
2539
2539
  end
2540
2540
  end
2541
2541
 
2542
+ context "Dataset#insert_sql" do
2543
+ setup do
2544
+ @ds = Sequel::Dataset.new(nil).from(:items)
2545
+ end
2546
+
2547
+ specify "should accept hash with symbol keys" do
2548
+ @ds.insert_sql(:c => 'd').should == "INSERT INTO items (c) VALUES ('d')"
2549
+ end
2550
+
2551
+ specify "should accept hash with string keys" do
2552
+ @ds.insert_sql('c' => 'd').should == "INSERT INTO items (c) VALUES ('d')"
2553
+ end
2554
+
2555
+ specify "should accept array subscript references" do
2556
+ @ds.insert_sql((:day|1) => 'd').should == "INSERT INTO items (day[1]) VALUES ('d')"
2557
+ end
2558
+ end
2559
+
2542
2560
  class DummyMummyDataset < Sequel::Dataset
2543
2561
  def first
2544
2562
  raise if @opts[:from] == [:a]
@@ -2592,3 +2610,51 @@ context "Dataset#table_exists?" do
2592
2610
  end
2593
2611
  end
2594
2612
 
2613
+ context "Dataset#inspect" do
2614
+ setup do
2615
+ @ds = Sequel::Dataset.new(nil).from(:blah)
2616
+ end
2617
+
2618
+ specify "should include the class name and the corresponding SQL statement" do
2619
+ @ds.inspect.should == '#<%s: %s>' % [@ds.class.to_s, @ds.sql.inspect]
2620
+ end
2621
+ end
2622
+
2623
+ context "Dataset#all" do
2624
+ setup do
2625
+ @c = Class.new(Sequel::Dataset) do
2626
+ def fetch_rows(sql, &block)
2627
+ block.call({:x => 1, :y => 2})
2628
+ block.call({:x => 3, :y => 4})
2629
+ block.call(sql)
2630
+ end
2631
+ end
2632
+ @dataset = @c.new(nil).from(:items)
2633
+ end
2634
+
2635
+ specify "should return an array with all records" do
2636
+ @dataset.all.should == [
2637
+ {:x => 1, :y => 2},
2638
+ {:x => 3, :y => 4},
2639
+ "SELECT * FROM items"
2640
+ ]
2641
+ end
2642
+
2643
+ specify "should accept options and pass them to #each" do
2644
+ @dataset.all(:limit => 33).should == [
2645
+ {:x => 1, :y => 2},
2646
+ {:x => 3, :y => 4},
2647
+ "SELECT * FROM items LIMIT 33"
2648
+ ]
2649
+ end
2650
+
2651
+ specify "should iterate over the array if a block is given" do
2652
+ a = []
2653
+
2654
+ @dataset.all do |r|
2655
+ a << (r.is_a?(Hash) ? r[:x] : r)
2656
+ end
2657
+
2658
+ a.should == [1, 3, "SELECT * FROM items"]
2659
+ end
2660
+ end
@@ -81,6 +81,7 @@ describe Sequel::Schema::AlterTableGenerator do
81
81
  set_column_default :eee, 1
82
82
  add_index [:fff, :ggg]
83
83
  drop_index :hhh
84
+ add_full_text_index :blah
84
85
  end
85
86
  end
86
87
 
@@ -92,7 +93,8 @@ describe Sequel::Schema::AlterTableGenerator do
92
93
  {:op => :set_column_type, :name => :ddd, :type => :float},
93
94
  {:op => :set_column_default, :name => :eee, :default => 1},
94
95
  {:op => :add_index, :columns => [:fff, :ggg]},
95
- {:op => :drop_index, :columns => [:hhh]}
96
+ {:op => :drop_index, :columns => [:hhh]},
97
+ {:op => :add_index, :columns => [:blah], :full_text => true}
96
98
  ]
97
99
  end
98
100
  end
data/spec/schema_spec.rb CHANGED
@@ -180,6 +180,15 @@ context "DB#create_table" do
180
180
  @db.sqls.should == ["CREATE TABLE cats (name text)", "CREATE UNIQUE INDEX cats_name_index ON cats (name)"]
181
181
  end
182
182
 
183
+ specify "should raise on full-text index definitions" do
184
+ proc {
185
+ @db.create_table(:cats) do
186
+ text :name
187
+ full_text_index :name
188
+ end
189
+ }.should raise_error(Sequel::Error)
190
+ end
191
+
183
192
  specify "should accept multiple index definitions" do
184
193
  @db.create_table(:cats) do
185
194
  integer :id
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.8.2
4
+ version: 1.0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-02-09 00:00:00 +02:00
12
+ date: 2008-02-11 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency