querybuilder 0.5.9 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/History.txt +29 -25
  2. data/Manifest.txt +20 -9
  3. data/README.rdoc +73 -10
  4. data/Rakefile +62 -30
  5. data/lib/extconf.rb +3 -0
  6. data/lib/query_builder.rb +39 -898
  7. data/lib/query_builder/error.rb +7 -0
  8. data/lib/query_builder/info.rb +3 -0
  9. data/lib/query_builder/parser.rb +80 -0
  10. data/lib/query_builder/processor.rb +714 -0
  11. data/lib/query_builder/query.rb +273 -0
  12. data/lib/querybuilder_ext.c +1870 -0
  13. data/lib/querybuilder_ext.rl +418 -0
  14. data/lib/querybuilder_rb.rb +1686 -0
  15. data/lib/querybuilder_rb.rl +214 -0
  16. data/lib/querybuilder_syntax.rl +47 -0
  17. data/old_QueryBuilder.rb +946 -0
  18. data/querybuilder.gemspec +42 -15
  19. data/tasks/build.rake +20 -0
  20. data/test/dummy_test.rb +21 -0
  21. data/test/mock/custom_queries/test.yml +5 -4
  22. data/test/mock/dummy.rb +9 -0
  23. data/test/mock/dummy_processor.rb +160 -0
  24. data/test/mock/queries/bar.yml +1 -1
  25. data/test/mock/queries/foo.yml +2 -2
  26. data/test/mock/user_processor.rb +34 -0
  27. data/test/query_test.rb +38 -0
  28. data/test/querybuilder/basic.yml +91 -0
  29. data/test/{query_builder → querybuilder}/custom.yml +11 -11
  30. data/test/querybuilder/errors.yml +32 -0
  31. data/test/querybuilder/filters.yml +115 -0
  32. data/test/querybuilder/group.yml +7 -0
  33. data/test/querybuilder/joins.yml +37 -0
  34. data/test/querybuilder/mixed.yml +18 -0
  35. data/test/querybuilder/rubyless.yml +15 -0
  36. data/test/querybuilder_test.rb +111 -0
  37. data/test/test_helper.rb +8 -3
  38. metadata +66 -19
  39. data/test/mock/dummy_query.rb +0 -114
  40. data/test/mock/user_query.rb +0 -55
  41. data/test/query_builder/basic.yml +0 -60
  42. data/test/query_builder/errors.yml +0 -50
  43. data/test/query_builder/filters.yml +0 -43
  44. data/test/query_builder/joins.yml +0 -25
  45. data/test/query_builder/mixed.yml +0 -12
  46. data/test/query_builder_test.rb +0 -36
@@ -1,114 +0,0 @@
1
- class DummyQuery < QueryBuilder
2
- set_main_table 'objects'
3
- set_main_class 'DummyQueryClass'
4
-
5
- load_custom_queries File.join(File.dirname(__FILE__), '*')
6
-
7
- # Build joins and filters from a relation.
8
- def parse_relation(rel, context)
9
- unless context_relation(rel, context) || direct_filter(rel, context) || join_relation(rel, context)
10
- @errors << "unknown relation '#{rel}'"
11
- end
12
- end
13
-
14
- # default context filter is to search in the current node's children (in self)
15
- def default_context_filter
16
- 'self'
17
- end
18
-
19
- private
20
-
21
- # Root filters (relations that can be solved without a join). Think 'in clause' (in self, in parent).
22
- def context_filter_fields(clause, is_last = false)
23
- case clause
24
- when 'self'
25
- ['parent_id', 'id']
26
- when 'parent'
27
- is_last ? ['parent_id', 'parent_id'] : ['parent_id', 'id']
28
- when 'project'
29
- is_last ? ['project_id', 'project_id'] : ['project_id', 'id']
30
- when 'site', main_table
31
- :void
32
- else
33
- nil
34
- end
35
- end
36
-
37
- def context_relation(clause, context)
38
- case clause
39
- when 'self'
40
- fields = ['id', 'id']
41
- when 'parent'
42
- fields = ['id', 'parent_id']
43
- when 'project'
44
- fields = ['id', 'project_id']
45
- when main_table, 'children'
46
- parse_context(default_context_filter) unless context
47
- return true # dummy clause: does nothing
48
- else
49
- return false
50
- end
51
-
52
- @where << "#{field_or_attr(fields[0])} = #{field_or_attr(fields[1], table(main_table,-1))}"
53
- end
54
-
55
- # Direct filter
56
- def direct_filter(rel, context)
57
- case rel
58
- when 'letters'
59
- parse_context(default_context_filter) unless context
60
- @where << "#{table}.kpath LIKE 'NNL%'"
61
- when 'clients'
62
- parse_context(default_context_filter) unless context
63
- @where << "#{table}.kpath LIKE 'NRCC%'"
64
- else
65
- return false
66
- end
67
- end
68
-
69
- def parse_change_class(rel, is_last)
70
- case rel
71
- when 'users'
72
- parse_context(default_context_filter, true) if is_last
73
- add_table('users')
74
- @where << "#{table('users')}.node_id = #{field_or_attr('id')}"
75
- return TestUserQuery # class change
76
- else
77
- return nil
78
- end
79
- end
80
-
81
- # Filters that need a join
82
- def join_relation(rel, context)
83
- case rel
84
- when 'recipients'
85
- fields = ['source_id', 4, 'target_id']
86
- when 'icons'
87
- fields = ['target_id', 5, 'source_id']
88
- when 'tags'
89
- # just to test joins
90
- needs_join_table('objects', 'INNER', 'tags', 'TABLE1.id = TABLE2.node_id')
91
- return true
92
- else
93
- return false
94
- end
95
-
96
- add_table('links')
97
- # source --> target
98
- @where << "#{field_or_attr('id')} = #{table('links')}.#{fields[2]} AND #{table('links')}.relation_id = #{fields[1]} AND #{table('links')}.#{fields[0]} = #{field_or_attr('id', table(main_table,-1))}"
99
- end
100
-
101
- # Overwrite this and take car to check for valid fields.
102
- def map_field(fld, table_name, is_null = false)
103
- if ['id', 'parent_id', 'project_id', 'section_id', 'kpath', 'name', 'event_at', 'custom_a'].include?(fld)
104
- "#{table_name}.#{fld}"
105
- else
106
- # error, raise
107
- end
108
- end
109
- end
110
-
111
- class DummyQueryClass
112
- def self.connection; self; end
113
- def self.quote(obj); "[[#{obj}]]"; end
114
- end
@@ -1,55 +0,0 @@
1
-
2
- class TestUser
3
- end
4
-
5
- class TestUserQuery < QueryBuilder
6
- set_main_table 'users'
7
- set_main_class 'TestUser'
8
-
9
- # Default sort order
10
- def default_order_clause
11
- "name ASC, first_name ASC"
12
- end
13
-
14
- def default_context_filter
15
- 'self'
16
- end
17
-
18
- def parse_change_class(rel, is_last)
19
- case rel
20
- when 'objects'
21
- parse_context(default_context_filter, true) if is_last
22
- add_table('objects')
23
- @where << "#{table('objects')}.id = #{field_or_attr('node_id')}"
24
- return TestUserQuery # class change
25
- else
26
- return nil
27
- end
28
- end
29
-
30
- def parse_relation(clause, context)
31
- return nil
32
- end
33
-
34
- def context_filter_fields(clause, is_last = false)
35
- nil
36
- end
37
-
38
- def parse_context(clause, is_last = false)
39
-
40
- if fields = context_filter_fields(clause, is_last)
41
- @where << "#{field_or_attr(fields[0])} = #{field_or_attr(fields[1], table(main_table,-1))}" if fields != :void
42
- else
43
- @errors << "invalid context '#{clause}'"
44
- end
45
- end
46
-
47
- # Overwrite this and take car to check for valid fields.
48
- def map_field(fld, table_name, context = nil)
49
- if ['id', 'name', 'first_name', 'node_id'].include?(fld)
50
- "#{table_name}.#{fld}"
51
- else
52
- # TODO: error, raise / ignore ?
53
- end
54
- end
55
- end
@@ -1,60 +0,0 @@
1
- empty:
2
- src: ""
3
- res: "[\"SELECT objects.* FROM objects WHERE objects.parent_id = ?\", id]"
4
-
5
- objects:
6
- res: "[\"SELECT objects.* FROM objects WHERE objects.parent_id = ?\", id]"
7
-
8
- objects_in_project:
9
- res: "[\"SELECT objects.* FROM objects WHERE objects.project_id = ?\", project_id]"
10
-
11
- objects_in_site:
12
- res: "\"SELECT objects.* FROM objects\""
13
-
14
- recipients:
15
- res: "[\"SELECT objects.* FROM objects,links WHERE objects.id = links.target_id AND links.relation_id = 4 AND links.source_id = ?\", id]"
16
- sql: "SELECT objects.* FROM objects,links WHERE objects.id = links.target_id AND links.relation_id = 4 AND links.source_id = [[123]]"
17
-
18
- letters_in_project:
19
- res: "[\"SELECT objects.* FROM objects WHERE objects.kpath LIKE 'NNL%' AND objects.project_id = ?\", project_id]"
20
-
21
- parent:
22
- res: "[\"SELECT objects.* FROM objects WHERE objects.id = ?\", parent_id]"
23
-
24
- order_single_table:
25
- src: "objects in project order by name ASC"
26
- res: "[\"SELECT objects.* FROM objects WHERE objects.project_id = ? ORDER BY objects.name ASC\", project_id]"
27
-
28
- order_many_tables:
29
- src: "recipients order by name ASC"
30
- res: "[\"SELECT objects.* FROM objects,links WHERE objects.id = links.target_id AND links.relation_id = 4 AND links.source_id = ? ORDER BY objects.name ASC\", id]"
31
-
32
- order_many_params:
33
- src: "objects in project order by name ASC, id DESC"
34
- res: "[\"SELECT objects.* FROM objects WHERE objects.project_id = ? ORDER BY objects.name ASC, objects.id DESC\", project_id]"
35
-
36
- limit:
37
- src: "objects in project limit 2"
38
- res: "[\"SELECT objects.* FROM objects WHERE objects.project_id = ? LIMIT 2\", project_id]"
39
-
40
- offset_in_limit:
41
- src: "objects in project limit 3,2"
42
- res: "[\"SELECT objects.* FROM objects WHERE objects.project_id = ? LIMIT 2 OFFSET 3\", project_id]"
43
-
44
- offset:
45
- src: "objects in project limit 2 offset 3"
46
- res: "[\"SELECT objects.* FROM objects WHERE objects.project_id = ? LIMIT 2 OFFSET 3\", project_id]"
47
-
48
- paginate:
49
- src: "objects in site limit 2 paginate p"
50
- res: "[\"SELECT objects.* FROM objects LIMIT 2 OFFSET ?\", ((p.to_i > 0 ? p.to_i : 1)-1)*2]"
51
-
52
- recipients_or_objects:
53
- src:
54
- - "recipients"
55
- - "objects"
56
- res: "[\"SELECT objects.* FROM objects,links WHERE ((objects.id = links.target_id AND links.relation_id = 4 AND links.source_id = ?) OR (objects.parent_id = ?)) GROUP BY objects.id\", id, id]"
57
-
58
- count_sql:
59
- src: "objects in project"
60
- count: "[\"SELECT COUNT(*) FROM objects WHERE objects.project_id = ?\", project_id]"
@@ -1,50 +0,0 @@
1
- bad_relation:
2
- src: "bolobolo"
3
- res: "unknown relation 'bolobolo'"
4
-
5
- bad_relation_in_alternate_query:
6
- src:
7
- - "bolobolo"
8
- - "letters in project"
9
- res: "unknown relation 'bolobolo'"
10
-
11
- order:
12
- src: "objects in project order by bad ASC"
13
- res: "invalid field 'bad'"
14
-
15
- offset_without_limit:
16
- src: "objects in project offset 3"
17
- res: "invalid offset clause '3' (used without limit)"
18
-
19
- ignore_warnings:
20
- context:
21
- ignore_warnings: yes
22
- src:
23
- - 'letters'
24
- - 'foo'
25
- res: "[\"SELECT objects.* FROM objects WHERE objects.kpath LIKE 'NNL%' AND objects.parent_id = ?\", id]"
26
-
27
- ignore_warnings_all_bad:
28
- context:
29
- ignore_warnings: yes
30
- src:
31
- - 'baz'
32
- - 'foo'
33
- res: "\"SELECT objects.* FROM objects WHERE 0\""
34
-
35
- do_not_ignore_warnings:
36
- context:
37
- ignore_warnings: no
38
- src:
39
- - 'letters'
40
- - 'foo'
41
- res: "unknown relation 'foo'"
42
-
43
- bad_equation:
44
- src: "objects where event_at > 2006-04-01'"
45
- res: "invalid clause \"event_at > 2006-04-01'\" near \"06-0\""
46
-
47
- bad_plus_plus:
48
- src: "objects where 1 + 3 + + 5 > event_at"
49
- res: "invalid clause \"1 + 3 + + 5 > event_at\" near \"+ + \""
50
-
@@ -1,43 +0,0 @@
1
- simple:
2
- src: "objects where name = 'foo' in site"
3
- res: "[\"SELECT objects.* FROM objects WHERE objects.name = ?\", \"foo\"]"
4
-
5
- recipients_that_are_clients:
6
- src: "recipients where kpath like 'NRCC%'"
7
- res: "[\"SELECT objects.* FROM objects,links WHERE objects.id = links.target_id AND links.relation_id = 4 AND links.source_id = ? AND objects.kpath LIKE ?\", id, \"NRCC%\"]"
8
-
9
- pages_in_site_name_like:
10
- src: "letters where name like 'a%' in site"
11
- res: "[\"SELECT objects.* FROM objects WHERE objects.kpath LIKE 'NNL%' AND objects.name LIKE ?\", \"a%\"]"
12
-
13
- pages_in_site_name_not_like:
14
- src: "letters where name not like 'a%' in site"
15
- res: "[\"SELECT objects.* FROM objects WHERE objects.kpath LIKE 'NNL%' AND objects.name NOT LIKE ?\", \"a%\"]"
16
-
17
- letters_group_by_name:
18
- res: "[\"SELECT objects.* FROM objects WHERE objects.kpath LIKE 'NNL%' AND objects.parent_id = ? GROUP BY objects.name\", id]"
19
-
20
- recipients_group_by_name:
21
- res: "[\"SELECT objects.* FROM objects,links WHERE objects.id = links.target_id AND links.relation_id = 4 AND links.source_id = ? GROUP BY objects.name\", id]"
22
-
23
- custom_query_having:
24
- context:
25
- custom_query_group: test
26
- src: "abc where number > 12"
27
- res: "\"SELECT a,34 AS number,c FROM test WHERE (34) > 12 AND 3 AND 2 AND 1 ORDER BY a ASC\""
28
-
29
- equation_in_filter:
30
- src: "objects where event_at > REF_DATE + custom_a months"
31
- res: "[\"SELECT objects.* FROM objects WHERE objects.parent_id = ? AND objects.event_at > now() + INTERVAL objects.custom_a MONTH\", id]"
32
-
33
- equation_in_filter_date:
34
- src: "objects where event_at > '2006-04-01' + 6 week"
35
- res: "[\"SELECT objects.* FROM objects WHERE objects.parent_id = ? AND objects.event_at > ? + INTERVAL 6 WEEK\", id, \"2006-04-01\"]"
36
-
37
- equation_and_or_par:
38
- src: "objects where event_at > '2006-04-01' or name like 'foo%'"
39
- res: "[\"SELECT objects.* FROM objects WHERE objects.parent_id = ? AND (objects.event_at > ? OR objects.name LIKE ?)\", id, \"2006-04-01\", \"foo%\"]"
40
-
41
- equation_par:
42
- src: "objects where (1 > 2 and 2 > 3) or 4 = 5"
43
- res: "[\"SELECT objects.* FROM objects WHERE objects.parent_id = ? AND ((1 > 2 AND 2 > 3) OR 4 = 5)\", id]"
@@ -1,25 +0,0 @@
1
-
2
- icons_from_recipients:
3
- res: "[\"SELECT ob1.* FROM objects,links,objects AS ob1,links AS li1 WHERE ob1.id = li1.source_id AND li1.relation_id = 5 AND li1.target_id = objects.id AND objects.id = links.target_id AND links.relation_id = 4 AND links.source_id = ? GROUP BY ob1.id\", id]"
4
-
5
- letters_from_recipients:
6
- res: "[\"SELECT ob1.* FROM objects,links,objects AS ob1 WHERE ob1.kpath LIKE 'NNL%' AND ob1.parent_id = objects.id AND objects.id = links.target_id AND links.relation_id = 4 AND links.source_id = ? GROUP BY ob1.id\", id]"
7
-
8
- objects_from_recipients:
9
- res: "[\"SELECT ob1.* FROM objects,links,objects AS ob1 WHERE ob1.parent_id = objects.id AND objects.id = links.target_id AND links.relation_id = 4 AND links.source_id = ? GROUP BY ob1.id\", id]"
10
-
11
- parent_from_parent:
12
- res: "[\"SELECT ob1.* FROM objects,objects AS ob1 WHERE ob1.id = objects.parent_id AND objects.id = ? GROUP BY ob1.id\", parent_id]"
13
-
14
- children_from_objects_in_project:
15
- res: "[\"SELECT ob1.* FROM objects,objects AS ob1 WHERE ob1.parent_id = objects.id AND objects.project_id = ? GROUP BY ob1.id\", project_id]"
16
-
17
- tags:
18
- res: "\"SELECT objects.* FROM objects INNER JOIN tags ON objects.id = tags.node_id\""
19
-
20
- letters_in_project_from_letters:
21
- res: "[\"SELECT ob1.* FROM objects,objects AS ob1 WHERE ob1.kpath LIKE 'NNL%' AND ob1.project_id = objects.id AND objects.kpath LIKE 'NNL%' AND objects.parent_id = ? GROUP BY ob1.id\", id]"
22
-
23
- letters_in_project_from_letters_group_by:
24
- src: "letters in project from letters group by name,id"
25
- res: "[\"SELECT ob1.* FROM objects,objects AS ob1 WHERE ob1.kpath LIKE 'NNL%' AND ob1.project_id = objects.id AND objects.kpath LIKE 'NNL%' AND objects.parent_id = ? GROUP BY ob1.name, ob1.id\", id]"
@@ -1,12 +0,0 @@
1
- users_from_objects:
2
- res: "TestUser: [\"SELECT users.* FROM objects,users WHERE users.node_id = objects.id AND objects.parent_id = ? ORDER BY users.name ASC, users.first_name ASC\", id]"
3
-
4
- users_where_name_from_objects:
5
- src: "users where name eq 'bob' from objects"
6
- res: "TestUser: [\"SELECT users.* FROM objects,users WHERE users.name = ? AND users.node_id = objects.id AND objects.parent_id = ? ORDER BY users.name ASC, users.first_name ASC\", \"bob\", id]"
7
-
8
- objects_from_users_from_objects:
9
- res: "TestUser: [\"SELECT users.* FROM objects,users,objects AS ob1 WHERE ob1.id = users.node_id AND users.node_id = objects.id AND objects.parent_id = ? ORDER BY users.name ASC, users.first_name ASC\", id]"
10
-
11
- users:
12
- res: "TestUser: [\"SELECT users.* FROM users WHERE users.node_id = objects.id AND objects.parent_id = ? ORDER BY users.name ASC, users.first_name ASC\", id]"
@@ -1,36 +0,0 @@
1
- require File.dirname(__FILE__) + '/test_helper.rb'
2
-
3
-
4
- class QueryBuilderTest < Test::Unit::TestCase
5
- yamltest
6
-
7
- def id; 123; end
8
- def parent_id; 333; end
9
- def project_id; 9999; end
10
- def connection; self; end
11
-
12
-
13
- def yt_parse(key, source, opts)
14
- opts = Hash[*(opts.map{|k,v| [k.to_sym, v]}.flatten)]
15
- query = DummyQuery.new(source, opts)
16
-
17
- case key
18
- when 'res'
19
- (query.main_class != DummyQueryClass ? "#{query.main_class.to_s}: " : '') + if res = query.to_s
20
- res
21
- else
22
- query.errors.join(", ")
23
- end
24
- when 'sql'
25
- query.sql(binding)
26
- when 'count'
27
- query.to_s(:count)
28
- when 'count_sql'
29
- query.sql(binding, :count)
30
- else
31
- "parse not implemented for '#{key}' in query_builder_test.rb"
32
- end
33
- end
34
-
35
- yt_make
36
- end