sequel_core 1.0.8.2 → 1.0.9.1

Sign up to get free protection for your applications and to get access to all the features.
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