querybuilder 1.1.3 → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,8 @@
1
+ == 1.1.4 2012-10-07
2
+
3
+ * Major enhancement
4
+ * Key/value tables are now 'LEFT' joined, enabling null testing and simplifying complex queries (with OR).
5
+
1
6
  == 1.1.3 2012-08-30
2
7
 
3
8
  * Major enhancement
@@ -1,3 +1,3 @@
1
1
  module QueryBuilder
2
- VERSION = '1.1.3'
2
+ VERSION = '1.1.4'
3
3
  end
@@ -791,16 +791,14 @@ module QueryBuilder
791
791
  end
792
792
 
793
793
  # Add a new table and apply scoping when needed
794
- def add_table(use_name, table_name = nil)
795
- if use_name == main_table && first?
796
- # we are now using final table
797
- context[:table_alias] = use_name
798
- avoid_alias = true
799
- else
800
- avoid_alias = false
801
- end
802
-
794
+ def add_table(use_name, table_name = nil, type = nil, &block)
803
795
  if use_name == main_table
796
+ if first?
797
+ # we are now using final table
798
+ context[:table_alias] = use_name
799
+ avoid_alias = true
800
+ end
801
+
804
802
  if context[:scope_type] == :join
805
803
  context[:scope_type] = nil
806
804
  # pre scope
@@ -808,7 +806,7 @@ module QueryBuilder
808
806
  @query.add_table(main_table, main_table, avoid_alias)
809
807
  apply_scope(context[:scope])
810
808
  end
811
- @query.add_table(use_name, table_name, avoid_alias)
809
+ @query.add_table(use_name, table_name, avoid_alias, type, &block)
812
810
  elsif context[:scope_type] == :filter
813
811
  context[:scope_type] = nil
814
812
  # post scope
@@ -816,12 +814,12 @@ module QueryBuilder
816
814
  apply_scope(context[:scope] || default_scope(context))
817
815
  else
818
816
  # scope already applied / skip
819
- @query.add_table(use_name, table_name, avoid_alias)
817
+ @query.add_table(use_name, table_name, avoid_alias, type, &block)
820
818
  end
821
819
  else
822
820
  # no scope
823
821
  # can only scope main_table
824
- @query.add_table(use_name, table_name)
822
+ @query.add_table(use_name, table_name, true, type, &block)
825
823
  end
826
824
  end
827
825
 
@@ -119,9 +119,9 @@ module QueryBuilder
119
119
 
120
120
  # 'avoid_alias' is used when parsing the last element so that it takes the real table name (nodes, not no1). We need
121
121
  # this because we can use 'OR' between parts and we thus need the same table reference.
122
- def add_table(use_name, table_name = nil, avoid_alias = true)
122
+ def add_table(use_name, table_name = nil, avoid_alias = true, type = nil, &block)
123
123
  alias_name = get_alias(use_name, table_name, avoid_alias)
124
- add_alias_to_tables(table_name || use_name, alias_name)
124
+ add_alias_to_tables(table_name || use_name, alias_name, type, &block)
125
125
  end
126
126
 
127
127
  # Add a table to 'import' a key/value based field. This method ensures that
@@ -133,10 +133,8 @@ module QueryBuilder
133
133
  # done, the index_table has been used for the given key in the current context
134
134
  else
135
135
  # insert the new table
136
- add_table(use_name, index_table, false)
136
+ add_table(use_name, index_table, false, :left, &block)
137
137
  alias_table = key_table[key] = table(use_name)
138
- # Let caller configure the filter (join).
139
- block.call(alias_table)
140
138
  end
141
139
  alias_table
142
140
  end
@@ -225,27 +223,51 @@ module QueryBuilder
225
223
  def quote_column_name(name)
226
224
  connection.quote_column_name(name)
227
225
  end
228
-
226
+
227
+ def has_table?(use_name)
228
+ !@table_alias[use_name].nil?
229
+ end
230
+
231
+
229
232
  private
230
- # Make sure each used table gets a unique name
233
+ # Make sure each used table gets a unique name. By default, this uses an alias
234
+ # But if 'avoid_alias' is true and it is the first call for this table, return the
235
+ # table without an alias.
231
236
  def get_alias(use_name, table_name = nil, avoid_alias = true)
232
237
  table_name ||= use_name
233
- @table_alias[use_name] ||= []
234
- if avoid_alias && !@tables.include?(table_name)
238
+ list = (@table_alias[use_name] ||= [])
239
+ if avoid_alias && !list.include?(use_name)
235
240
  alias_name = use_name
236
241
  elsif @tables.include?(use_name)
237
242
  # links, li1, li2, li3
238
- alias_name = "#{use_name[0..1]}#{@table_alias[use_name].size}"
243
+ alias_name = "#{use_name[0..1]}#{list.size}"
239
244
  else
240
245
  # ob1, obj2, objects
241
- alias_name = "#{use_name[0..1]}#{@table_alias[use_name].size + 1}"
246
+ alias_name = "#{use_name[0..1]}#{list.size + 1}"
242
247
  end
243
248
 
244
- @table_alias[use_name] << alias_name
249
+ list << alias_name
245
250
  alias_name
246
251
  end
247
252
 
248
- def add_alias_to_tables(table_name, alias_name)
253
+ def add_alias_to_tables(table_name, alias_name, type = nil, &block)
254
+ if type
255
+ key = "#{table_name}=#{type}=#{alias_name}"
256
+
257
+ @needed_join_tables[key] ||= begin
258
+ clause = "#{type.to_s.upcase} JOIN #{table_name}"
259
+ if alias_name && alias_name != table_name
260
+ clause << " AS #{alias_name}"
261
+ end
262
+ if block_given?
263
+ clause << " ON #{block.call(alias_name || table_name)}"
264
+ end
265
+ joins = (@join_tables[main_table] ||= [])
266
+ joins << clause
267
+ end
268
+ return
269
+ end
270
+
249
271
  if alias_name != table_name
250
272
  @tables << "#{table_name} AS #{alias_name}"
251
273
  else
@@ -278,7 +300,7 @@ module QueryBuilder
278
300
 
279
301
  group = @group
280
302
  if !group && @distinct
281
- key = @tables.size > 1 ? "#{main_table}.id" : 'id'
303
+ key = "#{main_table}.id"
282
304
 
283
305
  case self.class.adapter
284
306
  when 'postgresql'
data/querybuilder.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{querybuilder}
8
- s.version = "1.1.3"
8
+ s.version = "1.1.4"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Gaspard Bucher"]
12
- s.date = %q{2012-08-30}
12
+ s.date = %q{2012-10-10}
13
13
  s.description = %q{QueryBuilder is an interpreter for the "pseudo sql" language. This language
14
14
  can be used for two purposes:
15
15
 
data/test/database.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'active_record'
2
+ require 'logger'
2
3
 
3
4
  begin
4
5
  class QueryBuilderTestMigration < ActiveRecord::Migration
@@ -35,8 +35,7 @@ class DummyProcessor < QueryBuilder::Processor
35
35
  elsif %w{age size}.include?(field_name)
36
36
  tbl = add_key_value_table('idx', 'idx_nodes', field_name) do |tbl_name|
37
37
  # This block is only executed once
38
- add_filter "#{tbl_name}.node_id = #{table}.id"
39
- add_filter "#{tbl_name}.key = #{quote(field_name)}"
38
+ "#{tbl_name}.node_id = #{table}.id AND #{tbl_name}.key = #{quote(field_name)}"
40
39
  end
41
40
 
42
41
  "#{tbl}.value"
@@ -89,7 +89,7 @@ after_process_callback:
89
89
  context:
90
90
  after_filter: '(1 = 1)'
91
91
  src: "objects where name like 'a%' or name like 'b%' in site"
92
- res: "%Q{SELECT objects.* FROM objects WHERE (1 = 1) AND (objects.name LIKE 'a%' OR objects.name LIKE 'b%') GROUP BY id}"
92
+ res: "%Q{SELECT objects.* FROM objects WHERE (1 = 1) AND (objects.name LIKE 'a%' OR objects.name LIKE 'b%') GROUP BY objects.id}"
93
93
 
94
94
  function:
95
95
  src: "objects where event_at.year = 2005"
@@ -93,36 +93,36 @@ equation_with_date_interval:
93
93
  equation_and_or_par:
94
94
  src: "objects where event_at > '2006-04-01' or name like 'foo%'"
95
95
  sxp: '[:query, [:filter, [:relation, "objects"], [:or, [:>, [:field, "event_at"], [:string, "2006-04-01"]], [:like, [:field, "name"], [:string, "foo%"]]]]]'
96
- res: "[%Q{SELECT objects.* FROM objects WHERE (objects.event_at > '2006-04-01' OR objects.name LIKE 'foo%') AND objects.parent_id = ? GROUP BY id}, id]"
96
+ res: "[%Q{SELECT objects.* FROM objects WHERE (objects.event_at > '2006-04-01' OR objects.name LIKE 'foo%') AND objects.parent_id = ? GROUP BY objects.id}, id]"
97
97
 
98
98
  or_with_same_tables:
99
99
  src: "objects where age = 5 or age = 7"
100
- res: "[%Q{SELECT objects.* FROM idx_nodes AS id1,objects WHERE ((id1.value = 5 AND id1.key = 'age' AND id1.node_id = objects.id) OR (id1.value = 7 AND id1.key = 'age' AND id1.node_id = objects.id)) AND objects.parent_id = ? GROUP BY objects.id}, id]"
100
+ res: "[%Q{SELECT objects.* FROM objects LEFT JOIN idx_nodes AS id1 ON id1.node_id = objects.id AND id1.key = 'age' WHERE (id1.value = 5 OR id1.value = 7) AND objects.parent_id = ? GROUP BY objects.id}, id]"
101
101
 
102
102
  or_with_missing_table:
103
- src: "objects where age = 5 or 7"
104
- res: "[%Q{SELECT objects.* FROM idx_nodes AS id1,objects WHERE ((id1.value = 5 AND id1.key = 'age' AND id1.node_id = objects.id) OR (7 AND id1.id = 0)) AND objects.parent_id = ? GROUP BY objects.id}, id]"
103
+ src: "objects where age = 5 or id = 7"
104
+ res: "[%Q{SELECT objects.* FROM objects LEFT JOIN idx_nodes AS id1 ON id1.node_id = objects.id AND id1.key = 'age' WHERE (id1.value = 5 OR objects.id = 7) AND objects.parent_id = ? GROUP BY objects.id}, id]"
105
105
 
106
106
  or_different_table:
107
107
  src: "objects where age = 5 or size = 15"
108
- res: "[%Q{SELECT objects.* FROM idx_nodes AS id1,objects WHERE ((id1.value = 5 AND id1.key = 'age' AND id1.node_id = objects.id) OR (id1.value = 15 AND id1.key = 'size' AND id1.node_id = objects.id)) AND objects.parent_id = ? GROUP BY objects.id}, id]"
108
+ res: "[%Q{SELECT objects.* FROM objects LEFT JOIN idx_nodes AS id1 ON id1.node_id = objects.id AND id1.key = 'age' WHERE (id1.value = 5 OR id1.value = 15) AND objects.parent_id = ? GROUP BY objects.id}, id]"
109
109
 
110
110
  and_with_same_tables:
111
111
  src: "objects where age > 5 and age < 7"
112
- res: "[%Q{SELECT objects.* FROM idx_nodes AS id1,objects WHERE id1.value > 5 AND id1.value < 7 AND id1.key = 'age' AND id1.node_id = objects.id AND objects.parent_id = ?}, id]"
112
+ res: "[%Q{SELECT objects.* FROM objects LEFT JOIN idx_nodes AS id1 ON id1.node_id = objects.id AND id1.key = 'age' WHERE id1.value > 5 AND id1.value < 7 AND objects.parent_id = ?}, id]"
113
113
 
114
114
  and_with_same_tables_different_key:
115
115
  src: "objects where age > 5 and size = 10"
116
- res: "[%Q{SELECT objects.* FROM idx_nodes AS id1,idx_nodes AS id2,objects WHERE id1.value > 5 AND id2.value = 10 AND id2.key = 'size' AND id2.node_id = objects.id AND id1.key = 'age' AND id1.node_id = objects.id AND objects.parent_id = ?}, id]"
116
+ res: "[%Q{SELECT objects.* FROM objects LEFT JOIN idx_nodes AS id1 ON id1.node_id = objects.id AND id1.key = 'age' LEFT JOIN idx_nodes AS id2 ON id2.node_id = objects.id AND id2.key = 'size' WHERE id1.value > 5 AND id2.value = 10 AND objects.parent_id = ?}, id]"
117
117
 
118
118
 
119
119
  filter_and_order_index:
120
120
  src: "objects where age > 5 order by age asc"
121
- res: "[%Q{SELECT objects.* FROM idx_nodes AS id1,objects WHERE id1.value > 5 AND id1.key = 'age' AND id1.node_id = objects.id AND objects.parent_id = ? ORDER BY id1.value ASC}, id]"
121
+ res: "[%Q{SELECT objects.* FROM objects LEFT JOIN idx_nodes AS id1 ON id1.node_id = objects.id AND id1.key = 'age' WHERE id1.value > 5 AND objects.parent_id = ? ORDER BY id1.value ASC}, id]"
122
122
 
123
123
  order_without_idx_filter:
124
124
  src: "objects order by age asc"
125
- res: "[%Q{SELECT objects.* FROM idx_nodes AS id1,objects WHERE id1.key = 'age' AND id1.node_id = objects.id AND objects.parent_id = ? ORDER BY id1.value ASC}, id]"
125
+ res: "[%Q{SELECT objects.* FROM objects LEFT JOIN idx_nodes AS id1 ON id1.node_id = objects.id AND id1.key = 'age' WHERE objects.parent_id = ? ORDER BY id1.value ASC}, id]"
126
126
 
127
127
  equation_par:
128
128
  src: "objects where (1 > 2 or 2 > 3) and 4 = 5 "
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: querybuilder
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
8
  - 1
9
- - 3
10
- version: 1.1.3
9
+ - 4
10
+ version: 1.1.4
11
11
  platform: ruby
12
12
  authors:
13
13
  - Gaspard Bucher
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-08-30 00:00:00 +02:00
18
+ date: 2012-10-10 00:00:00 +02:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency