sequel_core 1.5.1 → 2.0.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 (68) hide show
  1. data/CHANGELOG +116 -0
  2. data/COPYING +19 -19
  3. data/README +83 -32
  4. data/Rakefile +9 -20
  5. data/bin/sequel +43 -112
  6. data/doc/cheat_sheet.rdoc +225 -0
  7. data/doc/dataset_filtering.rdoc +257 -0
  8. data/lib/sequel_core/adapters/adapter_skeleton.rb +4 -2
  9. data/lib/sequel_core/adapters/ado.rb +3 -1
  10. data/lib/sequel_core/adapters/db2.rb +4 -2
  11. data/lib/sequel_core/adapters/dbi.rb +127 -113
  12. data/lib/sequel_core/adapters/informix.rb +4 -2
  13. data/lib/sequel_core/adapters/jdbc.rb +5 -3
  14. data/lib/sequel_core/adapters/mysql.rb +112 -46
  15. data/lib/sequel_core/adapters/odbc.rb +5 -7
  16. data/lib/sequel_core/adapters/odbc_mssql.rb +12 -3
  17. data/lib/sequel_core/adapters/openbase.rb +3 -1
  18. data/lib/sequel_core/adapters/oracle.rb +11 -9
  19. data/lib/sequel_core/adapters/postgres.rb +261 -262
  20. data/lib/sequel_core/adapters/sqlite.rb +72 -22
  21. data/lib/sequel_core/connection_pool.rb +140 -73
  22. data/lib/sequel_core/core_ext.rb +201 -66
  23. data/lib/sequel_core/core_sql.rb +123 -153
  24. data/lib/sequel_core/database/schema.rb +156 -0
  25. data/lib/sequel_core/database.rb +321 -338
  26. data/lib/sequel_core/dataset/callback.rb +11 -12
  27. data/lib/sequel_core/dataset/convenience.rb +213 -240
  28. data/lib/sequel_core/dataset/pagination.rb +58 -43
  29. data/lib/sequel_core/dataset/parse_tree_sequelizer.rb +331 -0
  30. data/lib/sequel_core/dataset/query.rb +41 -0
  31. data/lib/sequel_core/dataset/schema.rb +15 -0
  32. data/lib/sequel_core/dataset/sequelizer.rb +41 -373
  33. data/lib/sequel_core/dataset/sql.rb +741 -632
  34. data/lib/sequel_core/dataset.rb +183 -168
  35. data/lib/sequel_core/deprecated.rb +1 -169
  36. data/lib/sequel_core/exceptions.rb +24 -19
  37. data/lib/sequel_core/migration.rb +44 -52
  38. data/lib/sequel_core/object_graph.rb +43 -42
  39. data/lib/sequel_core/pretty_table.rb +71 -76
  40. data/lib/sequel_core/schema/generator.rb +163 -105
  41. data/lib/sequel_core/schema/sql.rb +250 -93
  42. data/lib/sequel_core/schema.rb +2 -8
  43. data/lib/sequel_core/sql.rb +394 -0
  44. data/lib/sequel_core/worker.rb +37 -27
  45. data/lib/sequel_core.rb +99 -45
  46. data/spec/adapters/informix_spec.rb +0 -1
  47. data/spec/adapters/mysql_spec.rb +177 -124
  48. data/spec/adapters/oracle_spec.rb +0 -1
  49. data/spec/adapters/postgres_spec.rb +98 -58
  50. data/spec/adapters/sqlite_spec.rb +45 -4
  51. data/spec/blockless_filters_spec.rb +269 -0
  52. data/spec/connection_pool_spec.rb +21 -18
  53. data/spec/core_ext_spec.rb +169 -19
  54. data/spec/core_sql_spec.rb +56 -49
  55. data/spec/database_spec.rb +78 -17
  56. data/spec/dataset_spec.rb +300 -428
  57. data/spec/migration_spec.rb +1 -1
  58. data/spec/object_graph_spec.rb +5 -11
  59. data/spec/rcov.opts +1 -1
  60. data/spec/schema_generator_spec.rb +16 -4
  61. data/spec/schema_spec.rb +89 -10
  62. data/spec/sequelizer_spec.rb +56 -56
  63. data/spec/spec.opts +0 -5
  64. data/spec/spec_config.rb +7 -0
  65. data/spec/spec_config.rb.example +5 -5
  66. data/spec/spec_helper.rb +6 -0
  67. data/spec/worker_spec.rb +1 -1
  68. metadata +78 -63
@@ -1,35 +1,108 @@
1
- # Array extensions
1
+ # This file holds the extensions to core ruby classes that relate to the creation of SQL
2
+ # code.
3
+
2
4
  class Array
5
+ # Return a Sequel::SQL::ComplexExpression created from this array, not matching any of the
6
+ # conditions.
7
+ def ~
8
+ sql_expr_if_all_two_pairs(:OR, true)
9
+ end
10
+
11
+ # Return a Sequel::SQL::ComplexExpression created from this array, matching all of the
12
+ # conditions.
13
+ def sql_expr
14
+ sql_expr_if_all_two_pairs
15
+ end
16
+
17
+ # Return a Sequel::SQL::ComplexExpression created from this array, matching none
18
+ # of the conditions.
19
+ def sql_negate
20
+ sql_expr_if_all_two_pairs(:AND, true)
21
+ end
22
+
23
+ # Return a Sequel::SQL::ComplexExpression created from this array, matching any of the
24
+ # conditions.
25
+ def sql_or
26
+ sql_expr_if_all_two_pairs(:OR)
27
+ end
28
+
29
+ # Return a Sequel::SQL::ComplexExpression representing an SQL string made up of the
30
+ # concatenation of this array's elements. If an argument is passed
31
+ # it is used in between each element of the array in the SQL
32
+ # concatenation.
33
+ def sql_string_join(joiner=nil)
34
+ if joiner
35
+ args = self.inject([]) do |m, a|
36
+ m << a
37
+ m << joiner
38
+ end
39
+ args.pop
40
+ else
41
+ args = self
42
+ end
43
+ args = args.collect{|a| a.is_one_of?(Symbol, ::Sequel::SQL::Expression, ::Sequel::LiteralString, TrueClass, FalseClass, NilClass) ? a : a.to_s}
44
+ ::Sequel::SQL::ComplexExpression.new(:'||', *args)
45
+ end
46
+
3
47
  # Concatenates an array of strings into an SQL string. ANSI SQL and C-style
4
48
  # comments are removed, as well as excessive white-space.
5
49
  def to_sql
6
- map {|l| (l =~ /^(.*)--/ ? $1 : l).chomp}.join(' '). \
50
+ map {|l| ((m = /^(.*)--/.match(l)) ? m[1] : l).chomp}.join(' '). \
7
51
  gsub(/\/\*.*\*\//, '').gsub(/\s+/, ' ').strip
8
52
  end
9
- end
10
53
 
11
- module Sequel
12
- # LiteralString is used to represent literal SQL expressions. An
13
- # LiteralString is copied verbatim into an SQL statement. Instances of
14
- # LiteralString can be created by calling String#lit.
15
- class LiteralString < ::String
54
+ private
55
+
56
+ # Raise an error if this array is not made up of all two pairs, otherwise create a Sequel::SQL::ComplexExpression from this array.
57
+ def sql_expr_if_all_two_pairs(*args)
58
+ raise(Sequel::Error, 'Not all elements of the array are arrays of size 2, so it cannot be converted to an SQL expression') unless all_two_pairs?
59
+ ::Sequel::SQL::ComplexExpression.from_value_pairs(self, *args)
16
60
  end
17
61
  end
18
62
 
19
- # String extensions
20
- class String
21
- # Converts a string into an SQL string by removing comments.
22
- # See also Array#to_sql.
23
- def to_sql
24
- split("\n").to_sql
63
+ class Hash
64
+ # Return a Sequel::SQL::ComplexExpression created from this hash, matching
65
+ # all of the conditions in this hash and the condition specified by
66
+ # the given argument.
67
+ def &(ce)
68
+ ::Sequel::SQL::ComplexExpression.new(:AND, self, ce)
25
69
  end
26
-
27
- # Splits a string into separate SQL statements, removing comments
28
- # and excessive white-space.
29
- def split_sql
30
- to_sql.split(';').map {|s| s.strip}
70
+
71
+ # Return a Sequel::SQL::ComplexExpression created from this hash, matching
72
+ # all of the conditions in this hash or the condition specified by
73
+ # the given argument.
74
+ def |(ce)
75
+ ::Sequel::SQL::ComplexExpression.new(:OR, self, ce)
76
+ end
77
+
78
+ # Return a Sequel::SQL::ComplexExpression created from this hash, not matching any of the
79
+ # conditions.
80
+ def ~
81
+ ::Sequel::SQL::ComplexExpression.from_value_pairs(self, :OR, true)
82
+ end
83
+
84
+ # Return a Sequel::SQL::ComplexExpression created from this hash, matching all of the
85
+ # conditions.
86
+ def sql_expr
87
+ ::Sequel::SQL::ComplexExpression.from_value_pairs(self)
31
88
  end
32
89
 
90
+ # Return a Sequel::SQL::ComplexExpression created from this hash, matching none
91
+ # of the conditions.
92
+ def sql_negate
93
+ ::Sequel::SQL::ComplexExpression.from_value_pairs(self, :AND, true)
94
+ end
95
+
96
+ # Return a Sequel::SQL::ComplexExpression created from this hash, matching any of the
97
+ # conditions.
98
+ def sql_or
99
+ ::Sequel::SQL::ComplexExpression.from_value_pairs(self, :OR)
100
+ end
101
+ end
102
+
103
+ class String
104
+ include Sequel::SQL::ColumnMethods
105
+
33
106
  # Converts a string into an LiteralString, in order to override string
34
107
  # literalization, e.g.:
35
108
  #
@@ -42,155 +115,52 @@ class String
42
115
  def lit
43
116
  Sequel::LiteralString.new(self)
44
117
  end
45
-
46
118
  alias_method :expr, :lit
47
119
 
48
- # Converts a string into a Time object.
49
- def to_time
50
- begin
51
- Time.parse(self)
52
- rescue Exception => e
53
- raise Sequel::Error::InvalidValue, "Invalid time value '#{self}' (#{e.message})"
54
- end
55
- end
56
-
57
- # Converts a string into a Date object.
58
- def to_date
59
- begin
60
- Date.parse(self)
61
- rescue Exception => e
62
- raise Sequel::Error::InvalidValue, "Invalid date value '#{self}' (#{e.message})"
63
- end
120
+ # Splits a string into separate SQL statements, removing comments
121
+ # and excessive white-space.
122
+ def split_sql
123
+ to_sql.split(';').map {|s| s.strip}
64
124
  end
65
- end
66
-
67
125
 
68
- module Sequel
69
- module SQL
70
- module ColumnMethods
71
- AS = 'AS'.freeze
72
- DESC = 'DESC'.freeze
73
- ASC = 'ASC'.freeze
74
-
75
- def as(a); ColumnExpr.new(self, AS, a); end
76
-
77
- def desc; ColumnExpr.new(self, DESC); end
78
-
79
- def asc; ColumnExpr.new(self, ASC); end
80
-
81
- def cast_as(t)
82
- if t.is_a?(Symbol)
83
- t = t.to_s.lit
84
- end
85
- Sequel::SQL::Function.new(:cast, self.as(t))
86
- end
87
- end
88
-
89
- class Expression
90
- include ColumnMethods
91
- def lit; self; end
92
- end
93
-
94
- class ColumnExpr < Expression
95
- attr_reader :l, :op, :r
96
- def initialize(l, op, r = nil); @l, @op, @r = l, op, r; end
97
-
98
- def to_s(ds)
99
- @r ? \
100
- "#{ds.literal(@l)} #{@op} #{ds.literal(@r)}" : \
101
- "#{ds.literal(@l)} #{@op}"
102
- end
103
- end
104
-
105
- class QualifiedColumnRef < Expression
106
- def initialize(t, c); @t, @c = t, c; end
107
-
108
- def to_s(ds)
109
- "#{@t}.#{ds.literal(@c)}"
110
- end
111
- end
112
-
113
- class Function < Expression
114
- attr_reader :f, :args
115
- def initialize(f, *args); @f, @args = f, args; end
116
-
117
- # Functions are considered equivalent if they
118
- # have the same class, function, and arguments.
119
- def ==(x)
120
- x.class == self.class && @f == x.f && @args == x.args
121
- end
122
-
123
- def to_s(ds)
124
- args = @args.empty? ? '' : ds.literal(@args)
125
- "#{@f}(#{args})"
126
- end
127
- end
128
-
129
- class Subscript < Expression
130
- def initialize(f, sub); @f, @sub = f, sub; end
131
- def |(sub)
132
- unless Array === sub
133
- sub = [sub]
134
- end
135
- Subscript.new(@f, @sub << sub)
136
- end
137
-
138
- COMMA_SEPARATOR = ", ".freeze
139
-
140
- def to_s(ds)
141
- "#{@f}[#{@sub.join(COMMA_SEPARATOR)}]"
142
- end
143
- end
144
-
145
- class ColumnAll < Expression
146
- def initialize(t); @t = t; end
147
- def to_s(ds); "#{@t}.*"; end
148
- end
126
+ # Converts a string into an SQL string by removing comments.
127
+ # See also Array#to_sql.
128
+ def to_sql
129
+ split("\n").to_sql
149
130
  end
150
131
  end
151
132
 
152
- class String
153
- include Sequel::SQL::ColumnMethods
154
- end
155
-
156
133
  class Symbol
157
134
  include Sequel::SQL::ColumnMethods
158
- def *
135
+ include Sequel::SQL::ComplexExpressionMethods
136
+
137
+ # If no argument is given, returns a Sequel::SQL::ColumnAll object specifying all
138
+ # columns for this table.
139
+ # If an argument is given, returns a Sequel::SQL::ComplexExpression using the *
140
+ # (multiplication) operator with this and the given argument.
141
+ def *(ce=(arg=false;nil))
142
+ return super(ce) unless arg == false
159
143
  Sequel::SQL::ColumnAll.new(self);
160
144
  end
161
145
 
162
- def [](*args); Sequel::SQL::Function.new(self, *args); end
146
+ # Returns a Sequel::SQL::Function with this as the function name,
147
+ # and the given arguments.
148
+ def [](*args)
149
+ Sequel::SQL::Function.new(self, *args)
150
+ end
151
+
152
+ # If the given argument is an Integer or an array containing an Integer, returns
153
+ # a Sequel::SQL::Subscript with this column and the given arg.
154
+ # Otherwise returns a Sequel::SQL::ComplexExpression where this column (which should be boolean)
155
+ # or the given argument is true.
163
156
  def |(sub)
164
- unless Array === sub
165
- sub = [sub]
166
- end
167
- Sequel::SQL::Subscript.new(self, sub)
157
+ return super unless (Integer === sub) || ((Array === sub) && sub.any?{|x| Integer === x})
158
+ Sequel::SQL::Subscript.new(self, Array(sub))
168
159
  end
169
160
 
170
- COLUMN_REF_RE1 = /^(\w+)__(\w+)___(\w+)/.freeze
171
- COLUMN_REF_RE2 = /^(\w+)___(\w+)$/.freeze
172
- COLUMN_REF_RE3 = /^(\w+)__(\w+)$/.freeze
173
-
174
- # Converts a symbol into a column name. This method supports underscore
175
- # notation in order to express qualified (two underscores) and aliased
176
- # (three underscores) columns:
177
- #
178
- # ds = DB[:items]
179
- # :abc.to_column_ref(ds) #=> "abc"
180
- # :abc___a.to_column_ref(ds) #=> "abc AS a"
181
- # :items__abc.to_column_ref(ds) #=> "items.abc"
182
- # :items__abc___a.to_column_ref(ds) #=> "items.abc AS a"
183
- #
161
+ # Delegate the creation of the resulting SQL to the given dataset,
162
+ # since it may be database dependent.
184
163
  def to_column_ref(ds)
185
- case s = to_s
186
- when COLUMN_REF_RE1
187
- "#{$1}.#{ds.quote_column_ref($2)} AS #{ds.quote_column_ref($3)}"
188
- when COLUMN_REF_RE2
189
- "#{ds.quote_column_ref($1)} AS #{ds.quote_column_ref($2)}"
190
- when COLUMN_REF_RE3
191
- "#{$1}.#{ds.quote_column_ref($2)}"
192
- else
193
- ds.quote_column_ref(s)
194
- end
164
+ ds.symbol_to_column_ref(self)
195
165
  end
196
166
  end
@@ -0,0 +1,156 @@
1
+ module Sequel
2
+ class Database
3
+ # Adds a column to the specified table. This method expects a column name,
4
+ # a datatype and optionally a hash with additional constraints and options:
5
+ #
6
+ # DB.add_column :items, :name, :text, :unique => true, :null => false
7
+ # DB.add_column :items, :category, :text, :default => 'ruby'
8
+ #
9
+ # See alter_table.
10
+ def add_column(table, *args)
11
+ alter_table(table) {add_column(*args)}
12
+ end
13
+
14
+ # Adds an index to a table for the given columns:
15
+ #
16
+ # DB.add_index :posts, :title
17
+ # DB.add_index :posts, [:author, :title], :unique => true
18
+ #
19
+ # See alter_table.
20
+ def add_index(table, *args)
21
+ alter_table(table) {add_index(*args)}
22
+ end
23
+
24
+ # Alters the given table with the specified block. Here are the currently
25
+ # available operations:
26
+ #
27
+ # DB.alter_table :items do
28
+ # add_column :category, :text, :default => 'ruby'
29
+ # drop_column :category
30
+ # rename_column :cntr, :counter
31
+ # set_column_type :value, :float
32
+ # set_column_default :value, :float
33
+ # add_index [:group, :category]
34
+ # drop_index [:group, :category]
35
+ # end
36
+ #
37
+ # Note that #add_column accepts all the options available for column
38
+ # definitions using create_table, and #add_index accepts all the options
39
+ # available for index definition.
40
+ #
41
+ # See Schema::AlterTableGenerator.
42
+ def alter_table(name, &block)
43
+ g = Schema::AlterTableGenerator.new(self, &block)
44
+ alter_table_sql_list(name, g.operations).each {|sql| execute(sql)}
45
+ end
46
+
47
+ # Creates a table with the columns given in the provided block:
48
+ #
49
+ # DB.create_table :posts do
50
+ # primary_key :id, :serial
51
+ # column :title, :text
52
+ # column :content, :text
53
+ # index :title
54
+ # end
55
+ #
56
+ # See Schema::Generator.
57
+ def create_table(name, &block)
58
+ g = Schema::Generator.new(self, &block)
59
+ create_table_sql_list(name, *g.create_info).each {|sql| execute(sql)}
60
+ end
61
+
62
+ # Forcibly creates a table. If the table already exists it is dropped.
63
+ def create_table!(name, &block)
64
+ drop_table(name) rescue nil
65
+ create_table(name, &block)
66
+ end
67
+
68
+ # Creates a view, replacing it if it already exists:
69
+ #
70
+ # DB.create_or_replace_view(:cheap_items, "SELECT * FROM items WHERE price < 100")
71
+ # DB.create_or_replace_view(:ruby_items, DB[:items].filter(:category => 'ruby'))
72
+ def create_or_replace_view(name, source)
73
+ source = source.sql if source.is_a?(Dataset)
74
+ execute("CREATE OR REPLACE VIEW #{name} AS #{source}")
75
+ end
76
+
77
+ # Creates a view based on a dataset or an SQL string:
78
+ #
79
+ # DB.create_view(:cheap_items, "SELECT * FROM items WHERE price < 100")
80
+ # DB.create_view(:ruby_items, DB[:items].filter(:category => 'ruby'))
81
+ def create_view(name, source)
82
+ source = source.sql if source.is_a?(Dataset)
83
+ execute("CREATE VIEW #{name} AS #{source}")
84
+ end
85
+
86
+ # Removes a column from the specified table:
87
+ #
88
+ # DB.drop_column :items, :category
89
+ #
90
+ # See alter_table.
91
+ def drop_column(table, *args)
92
+ alter_table(table) {drop_column(*args)}
93
+ end
94
+
95
+ # Removes an index for the given table and column/s:
96
+ #
97
+ # DB.drop_index :posts, :title
98
+ # DB.drop_index :posts, [:author, :title]
99
+ #
100
+ # See alter_table.
101
+ def drop_index(table, columns)
102
+ alter_table(table) {drop_index(columns)}
103
+ end
104
+
105
+ # Drops one or more tables corresponding to the given table names:
106
+ #
107
+ # DB.drop_table(:posts, :comments)
108
+ def drop_table(*names)
109
+ names.each {|n| execute(drop_table_sql(n))}
110
+ end
111
+
112
+ # Drops a view:
113
+ #
114
+ # DB.drop_view(:cheap_items)
115
+ def drop_view(name)
116
+ execute("DROP VIEW #{name}")
117
+ end
118
+
119
+ # Renames a table:
120
+ #
121
+ # DB.tables #=> [:items]
122
+ # DB.rename_table :items, :old_items
123
+ # DB.tables #=> [:old_items]
124
+ def rename_table(*args)
125
+ execute(rename_table_sql(*args))
126
+ end
127
+
128
+ # Renames a column in the specified table. This method expects the current
129
+ # column name and the new column name:
130
+ #
131
+ # DB.rename_column :items, :cntr, :counter
132
+ #
133
+ # See alter_table.
134
+ def rename_column(table, *args)
135
+ alter_table(table) {rename_column(*args)}
136
+ end
137
+
138
+ # Sets the default value for the given column in the given table:
139
+ #
140
+ # DB.set_column_default :items, :category, 'perl!'
141
+ #
142
+ # See alter_table.
143
+ def set_column_default(table, *args)
144
+ alter_table(table) {set_column_default(*args)}
145
+ end
146
+
147
+ # Set the data type for the given column in the given table:
148
+ #
149
+ # DB.set_column_type :items, :price, :float
150
+ #
151
+ # See alter_table.
152
+ def set_column_type(table, *args)
153
+ alter_table(table) {set_column_type(*args)}
154
+ end
155
+ end
156
+ end