babik 0.1.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 (109) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +16 -0
  3. data/README.md +718 -0
  4. data/Rakefile +18 -0
  5. data/lib/babik.rb +122 -0
  6. data/lib/babik/database.rb +16 -0
  7. data/lib/babik/queryset.rb +154 -0
  8. data/lib/babik/queryset/components/aggregation.rb +172 -0
  9. data/lib/babik/queryset/components/limit.rb +22 -0
  10. data/lib/babik/queryset/components/order.rb +161 -0
  11. data/lib/babik/queryset/components/projection.rb +118 -0
  12. data/lib/babik/queryset/components/select_related.rb +78 -0
  13. data/lib/babik/queryset/components/sql_renderer.rb +99 -0
  14. data/lib/babik/queryset/components/where.rb +43 -0
  15. data/lib/babik/queryset/lib/association/foreign_association_chain.rb +97 -0
  16. data/lib/babik/queryset/lib/association/select_related_association_chain.rb +32 -0
  17. data/lib/babik/queryset/lib/condition.rb +103 -0
  18. data/lib/babik/queryset/lib/field.rb +34 -0
  19. data/lib/babik/queryset/lib/join/association_joiner.rb +39 -0
  20. data/lib/babik/queryset/lib/join/join.rb +86 -0
  21. data/lib/babik/queryset/lib/selection/config.rb +19 -0
  22. data/lib/babik/queryset/lib/selection/foreign_selection.rb +39 -0
  23. data/lib/babik/queryset/lib/selection/local_selection.rb +40 -0
  24. data/lib/babik/queryset/lib/selection/operation/base.rb +126 -0
  25. data/lib/babik/queryset/lib/selection/operation/date.rb +178 -0
  26. data/lib/babik/queryset/lib/selection/operation/operations.rb +201 -0
  27. data/lib/babik/queryset/lib/selection/operation/regex.rb +58 -0
  28. data/lib/babik/queryset/lib/selection/path/foreign_path.rb +50 -0
  29. data/lib/babik/queryset/lib/selection/path/local_path.rb +44 -0
  30. data/lib/babik/queryset/lib/selection/path/path.rb +23 -0
  31. data/lib/babik/queryset/lib/selection/select_related_selection.rb +38 -0
  32. data/lib/babik/queryset/lib/selection/selection.rb +19 -0
  33. data/lib/babik/queryset/lib/update/assignment.rb +108 -0
  34. data/lib/babik/queryset/mixins/aggregatable.rb +17 -0
  35. data/lib/babik/queryset/mixins/bounded.rb +38 -0
  36. data/lib/babik/queryset/mixins/clonable.rb +52 -0
  37. data/lib/babik/queryset/mixins/countable.rb +44 -0
  38. data/lib/babik/queryset/mixins/deletable.rb +13 -0
  39. data/lib/babik/queryset/mixins/distinguishable.rb +27 -0
  40. data/lib/babik/queryset/mixins/filterable.rb +51 -0
  41. data/lib/babik/queryset/mixins/limitable.rb +88 -0
  42. data/lib/babik/queryset/mixins/lockable.rb +31 -0
  43. data/lib/babik/queryset/mixins/none.rb +16 -0
  44. data/lib/babik/queryset/mixins/projectable.rb +34 -0
  45. data/lib/babik/queryset/mixins/related_selector.rb +28 -0
  46. data/lib/babik/queryset/mixins/set_operations.rb +32 -0
  47. data/lib/babik/queryset/mixins/sortable.rb +49 -0
  48. data/lib/babik/queryset/mixins/sql_renderizable.rb +17 -0
  49. data/lib/babik/queryset/mixins/updatable.rb +14 -0
  50. data/lib/babik/queryset/templates/default/delete/main.sql.erb +14 -0
  51. data/lib/babik/queryset/templates/default/select/components/aggregation.sql.erb +5 -0
  52. data/lib/babik/queryset/templates/default/select/components/from.sql.erb +16 -0
  53. data/lib/babik/queryset/templates/default/select/components/from_set.sql.erb +3 -0
  54. data/lib/babik/queryset/templates/default/select/components/from_table.sql.erb +2 -0
  55. data/lib/babik/queryset/templates/default/select/components/limit.sql.erb +10 -0
  56. data/lib/babik/queryset/templates/default/select/components/order_by.sql.erb +9 -0
  57. data/lib/babik/queryset/templates/default/select/components/projection.sql.erb +7 -0
  58. data/lib/babik/queryset/templates/default/select/components/select_related.sql.erb +26 -0
  59. data/lib/babik/queryset/templates/default/select/components/where.sql.erb +39 -0
  60. data/lib/babik/queryset/templates/default/select/main.sql.erb +42 -0
  61. data/lib/babik/queryset/templates/default/update/main.sql.erb +15 -0
  62. data/lib/babik/queryset/templates/mssql/select/components/limit.sql.erb +8 -0
  63. data/lib/babik/queryset/templates/mssql/select/components/order_by.sql.erb +21 -0
  64. data/lib/babik/queryset/templates/mysql2/delete/main.sql.erb +15 -0
  65. data/lib/babik/queryset/templates/mysql2/update/main.sql.erb +18 -0
  66. data/lib/babik/queryset/templates/sqlite3/select/components/from_set.sql.erb +5 -0
  67. data/test/config/db/schema.rb +83 -0
  68. data/test/config/models/bad_post.rb +5 -0
  69. data/test/config/models/bad_tag.rb +5 -0
  70. data/test/config/models/category.rb +4 -0
  71. data/test/config/models/geozone.rb +6 -0
  72. data/test/config/models/group.rb +5 -0
  73. data/test/config/models/group_user.rb +5 -0
  74. data/test/config/models/post.rb +24 -0
  75. data/test/config/models/post_tag.rb +5 -0
  76. data/test/config/models/tag.rb +5 -0
  77. data/test/config/models/user.rb +6 -0
  78. data/test/delete/delete_test.rb +60 -0
  79. data/test/delete/foreign_conditions_delete_test.rb +57 -0
  80. data/test/delete/local_conditions_delete_test.rb +20 -0
  81. data/test/enable_coverage.rb +17 -0
  82. data/test/lib/selection/operation/log/test-queries.log +1 -0
  83. data/test/lib/selection/operation/test_date.rb +131 -0
  84. data/test/lib/selection/operation/test_regex.rb +55 -0
  85. data/test/other/clone_test.rb +129 -0
  86. data/test/other/escape_test.rb +21 -0
  87. data/test/other/inverse_of_required_test.rb +33 -0
  88. data/test/select/aggregate_test.rb +151 -0
  89. data/test/select/bounds_test.rb +46 -0
  90. data/test/select/count_test.rb +147 -0
  91. data/test/select/distinct_test.rb +38 -0
  92. data/test/select/exclude_test.rb +72 -0
  93. data/test/select/filter_from_object_test.rb +125 -0
  94. data/test/select/filter_test.rb +207 -0
  95. data/test/select/for_update_test.rb +19 -0
  96. data/test/select/foreign_selection_test.rb +60 -0
  97. data/test/select/get_test.rb +40 -0
  98. data/test/select/limit_test.rb +109 -0
  99. data/test/select/local_selection_test.rb +24 -0
  100. data/test/select/lookup_test.rb +208 -0
  101. data/test/select/none_test.rb +40 -0
  102. data/test/select/order_test.rb +165 -0
  103. data/test/select/project_test.rb +107 -0
  104. data/test/select/select_related_test.rb +124 -0
  105. data/test/select/subquery_test.rb +50 -0
  106. data/test/set_operations/basic_usage_test.rb +121 -0
  107. data/test/test_helper.rb +55 -0
  108. data/test/update/update_test.rb +93 -0
  109. metadata +278 -0
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Babik
4
+ module QuerySet
5
+ # Functionality related to the UPDATE operation
6
+ module Updatable
7
+ # Runs the update
8
+ # @param update_command [Hash{field: value}] Runs the update query.
9
+ def update(update_command)
10
+ self.model.connection.execute(sql.update(update_command))
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ <%#
2
+ SQL default delete.
3
+ Most DBMS engines optimize IN if there is a simple subquery inside pulling up the conditions as joins.
4
+ %>
5
+
6
+ <% model = queryset.model %>
7
+
8
+ DELETE
9
+ FROM <%= model.table_name %>
10
+ WHERE id IN (
11
+ SELECT id FROM (
12
+ <%= queryset.sql.select %>
13
+ ) AS subquery
14
+ )
@@ -0,0 +1,5 @@
1
+ <% if queryset.aggregation? %>
2
+
3
+ <%= queryset._aggregation.sql %>
4
+
5
+ <% end %>
@@ -0,0 +1,16 @@
1
+
2
+ <% if queryset.class == Babik::QuerySet::Base %>
3
+
4
+ FROM
5
+ <%= render.('select/components/from_table.sql.erb', {queryset: queryset}) %>
6
+
7
+ <% elsif queryset.is_a?(Babik::QuerySet::SetOperation) %>
8
+
9
+ FROM
10
+ <%= render.('select/components/from_set.sql.erb', {queryset: queryset}) %>
11
+
12
+ <% else %>
13
+
14
+ Operation NOT recognized
15
+
16
+ <% end %>
@@ -0,0 +1,3 @@
1
+ (
2
+ (<%= queryset.left_operand.sql.select %>) <%= queryset.operation %> ( <%= queryset.right_operand.sql.select %>)
3
+ ) AS <%= queryset.model.table_name %>
@@ -0,0 +1,2 @@
1
+
2
+ <%= queryset.model.table_name %>
@@ -0,0 +1,10 @@
1
+ <% limit = queryset._limit %>
2
+
3
+ <% if limit %>
4
+
5
+ LIMIT <%= limit.size %>
6
+ <% if limit.offset %>
7
+ OFFSET <%= limit.offset %>
8
+ <% end %>
9
+
10
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <% order = queryset._order %>
2
+
3
+ <%# Order the results %>
4
+ <% if order %>
5
+
6
+ ORDER BY
7
+ <%= order.sql %>
8
+
9
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <% if queryset.projection? %>
2
+
3
+ <% projection = queryset._projection %>
4
+
5
+ <%= projection.sql %>
6
+
7
+ <% end %>
@@ -0,0 +1,26 @@
1
+ <% select_related = queryset._select_related %>
2
+ <% associations = select_related.associations %>
3
+
4
+ <%# Include each association target model in the select_related call in the SQL SELECT %>
5
+ <% associations.each_with_index do |association, association_index| %>
6
+
7
+ <% last_one_association = association_index == associations.length - 1 %>
8
+
9
+ <% target_alias = association.target_alias %>
10
+ <% target_model = association.target_model %>
11
+
12
+ <%# Include each column of the target model in the SQL SELECT %>
13
+ <% target_model.column_names.each_with_index do |target_field, target_field_index| %>
14
+
15
+ <% last_one_target_field = target_field_index == target_model.column_names.length - 1 %>
16
+
17
+ <%# Each column is included with an alias. That alias is the association path replacing :: with __ %>
18
+ <%= target_alias %>.<%= target_field %>
19
+ AS
20
+ <%= association.id %>__<%= target_field %>
21
+
22
+ <% unless last_one_target_field %>,<% end %>
23
+ <% end %>
24
+
25
+ <% unless last_one_association %>,<% end %>
26
+ <% end %>
@@ -0,0 +1,39 @@
1
+ <% where = queryset._where %>
2
+
3
+ <% if where.inclusion_filters? || where.exclusion_filters? %>
4
+
5
+ WHERE (
6
+ <% if where.inclusion_filters? %>
7
+ (
8
+ <% inclusion_filters = where.inclusion_filters %>
9
+ <% inclusion_filters.each_with_index do |condition, condition_index| %>
10
+ <% last_one = condition_index == inclusion_filters.length - 1 %>
11
+ (
12
+ <%= condition.sql %>
13
+ )
14
+ <% unless last_one %>AND<% end %>
15
+ <% end %>
16
+ )
17
+ <% end %>
18
+
19
+ <% if where.inclusion_filters? && where.exclusion_filters? %>
20
+ AND
21
+ <% end %>
22
+
23
+ <% if where.exclusion_filters? %>
24
+ ( NOT
25
+ (
26
+ <% exclusion_filters = where.exclusion_filters %>
27
+ <% exclusion_filters.each_with_index do |condition, condition_index| %>
28
+ <% last_one = condition_index == exclusion_filters.length - 1 %>
29
+ (
30
+ <%= condition.sql %>
31
+ )
32
+ <% unless last_one %>AND<% end %>
33
+ <% end %>
34
+ )
35
+ )
36
+ <% end %>
37
+ )
38
+
39
+ <% end %>
@@ -0,0 +1,42 @@
1
+ <% model = queryset.model %>
2
+
3
+ SELECT
4
+
5
+ <% if queryset.lock? %>
6
+ <%= queryset._lock_type %>
7
+ <% end %>
8
+
9
+ <% if queryset.distinct? %>
10
+ DISTINCT
11
+ <% end %>
12
+
13
+ <% if queryset.aggregation? %>
14
+ <%= render.('select/components/aggregation.sql.erb', {queryset: queryset}) %>
15
+ <% else %>
16
+ <% if queryset.projection? %>
17
+ <%# Projection of the results %>
18
+ <%= render.('select/components/projection.sql.erb', {queryset: queryset}) %>
19
+ <% else %>
20
+ <%= model.table_name %>.*
21
+ <% if queryset.select_related? %>
22
+ ,
23
+ <%= render.('select/components/select_related.sql.erb', {queryset: queryset}) %>
24
+ <% end %>
25
+ <% end %>
26
+
27
+ <% end %>
28
+
29
+ <%# From %>
30
+ <%= render.('select/components/from.sql.erb', {queryset: queryset}) %>
31
+
32
+ <%# Left joins %>
33
+ <%= queryset.sql.left_joins %>
34
+
35
+ <%# Where conditions %>
36
+ <%= render.('select/components/where.sql.erb', {queryset: queryset}) %>
37
+
38
+ <%# Order the results %>
39
+ <%= render.('select/components/order_by.sql.erb', {queryset: queryset}) %>
40
+
41
+ <%# Limit the results (only MySQL, MariaDB and PostgreSQL) %>
42
+ <%= render.('select/components/limit.sql.erb', {queryset: queryset}) %>
@@ -0,0 +1,15 @@
1
+ <%#
2
+ SQL default update.
3
+ Most DBMS engines optimize IN if there is a simple subquery inside pulling up the conditions as joins
4
+ %>
5
+
6
+ <% model = queryset.model %>
7
+
8
+ UPDATE <%= model.table_name %>
9
+ SET
10
+ <% update_command.each do |field, value| %>
11
+ <%= Babik::QuerySet::Update::Assignment.sql_field(model, field) %> = <%= Babik::QuerySet::Update::Assignment.sql_value(value) %>
12
+ <% end %>
13
+ WHERE id IN (
14
+ <%= queryset.sql.select %>
15
+ )
@@ -0,0 +1,8 @@
1
+ <% limit = queryset._limit %>
2
+
3
+ <% if limit %>
4
+
5
+ OFFSET <%= limit.offset ? limit.offset : 0 %> ROWS
6
+ FETCH NEXT <%= limit.size %> ROWS ONLY
7
+
8
+ <% end %>
@@ -0,0 +1,21 @@
1
+ <% order = queryset._order %>
2
+
3
+
4
+ <%# Order the results %>
5
+ <% if order %>
6
+
7
+ ORDER BY
8
+ <%= order.sql %>
9
+
10
+ <% else %>
11
+
12
+ <% limit = queryset._limit %>
13
+
14
+ <%# MSSQL server requires always an ORDER BY if a OFFSET - FETCH NEXT clause is present %>
15
+ <% if limit %>
16
+
17
+ ORDER BY <%= queryset.model.table_name %>.id ASC
18
+
19
+ <% end %>
20
+
21
+ <% end %>
@@ -0,0 +1,15 @@
1
+ <%#
2
+ SQL default delete.
3
+ Most DBMS engines optimize IN if there is a simple subquery inside pulling up the conditions as joins.
4
+ %>
5
+
6
+ <% model = queryset.model %>
7
+
8
+
9
+ DELETE <%= model.table_name %>.*
10
+ FROM <%= model.table_name %> <%= model.table_name %>
11
+
12
+ <%= queryset.sql.left_joins %>
13
+
14
+ <%# Where conditions %>
15
+ <%= render.('select/components/where.sql.erb', {queryset: queryset}) %>
@@ -0,0 +1,18 @@
1
+ <%#
2
+ SQL default update.
3
+ Most DBMS engines optimize IN if there is a simple subquery inside pulling up the conditions as joins
4
+ %>
5
+
6
+ <% model = queryset.model %>
7
+
8
+ UPDATE <%= model.table_name %> AS <%= model.table_name %>
9
+
10
+ <%= queryset.sql.left_joins %>
11
+
12
+ SET
13
+ <% update_command.each do |field, value| %>
14
+ <%= model.table_name %>.<%= Babik::QuerySet::Update::Assignment.sql_field(model, field) %> = <%= Babik::QuerySet::Update::Assignment.sql_value(value) %>
15
+ <% end %>
16
+
17
+ <%# Where conditions %>
18
+ <%= render.('select/components/where.sql.erb', {queryset: queryset}) %>
@@ -0,0 +1,5 @@
1
+ (
2
+ <%= queryset.left_operand.sql.select %>
3
+ <%= queryset.operation %>
4
+ <%= queryset.right_operand.sql.select %>
5
+ ) AS <%= queryset.model.table_name %>
@@ -0,0 +1,83 @@
1
+ ActiveRecord::Schema.define do
2
+ self.verbose = false
3
+
4
+ create_table :geo_zones, force: true do |t|
5
+ t.string :name
6
+ t.text :description
7
+ t.integer :parent_zone_id
8
+ t.timestamps
9
+ end
10
+
11
+ create_table :group, force: true do |t|
12
+ t.string :name
13
+ t.string :description
14
+ t.timestamps
15
+ end
16
+
17
+ create_table :group_users, force: true do |t|
18
+ t.integer :group_id
19
+ t.integer :user_id
20
+ t.timestamps
21
+ end
22
+
23
+ create_table :users, force: true do |t|
24
+ t.integer :zone_id
25
+ t.string :first_name
26
+ t.string :last_name
27
+ t.text :biography
28
+ t.integer :age
29
+ t.string :email
30
+ t.timestamps
31
+ end
32
+
33
+ create_table :posts, force: true do |t|
34
+ t.string :title
35
+ t.text :content
36
+ t.integer :stars
37
+ t.integer :author_id
38
+ t.integer :category_id
39
+ t.timestamps
40
+ end
41
+
42
+ create_table :post_tags, force: true do |t|
43
+ t.integer :post_id
44
+ t.integer :tag_id
45
+ t.timestamps
46
+ end
47
+
48
+ create_table :tags, force: true do |t|
49
+ t.string :name
50
+ t.timestamps
51
+ end
52
+
53
+ create_table :categories, force: true do |t|
54
+ t.string :name
55
+ t.timestamps
56
+ end
57
+
58
+ # Post class only used to show what happens if a model
59
+ # has no inverse_of in one of its associations
60
+ create_table :bad_posts, force: true do |t|
61
+ t.string :title
62
+ t.text :content
63
+ t.integer :stars
64
+ t.integer :category_id
65
+ t.timestamps
66
+ end
67
+
68
+ # Tag class that will make use of a has_and_belongs_to_many relationship
69
+ # Used only for test the detection of has_and_belongs_to_many and the raise
70
+ # of an exception happens
71
+ create_table :bad_tags, force: true do |t|
72
+ t.string :name
73
+ t.integer :bad_post_id
74
+ t.timestamps
75
+ end
76
+
77
+ add_index :users, :email, unique: true
78
+ add_index :group_users, [:group_id, :user_id], unique: true
79
+ add_index :categories, :name, unique: true
80
+ add_index :post_tags, [:post_id, :tag_id], unique: true
81
+ add_index :posts, [:title, :author_id], unique: true
82
+ add_index :tags, :name, unique: true
83
+ end
@@ -0,0 +1,5 @@
1
+
2
+ class BadPost < ActiveRecord::Base
3
+ belongs_to :category
4
+ has_many :bad_tags
5
+ end
@@ -0,0 +1,5 @@
1
+
2
+ class BadTag < ActiveRecord::Base
3
+ has_and_belongs_to_many :posts
4
+ belongs_to :bad_post
5
+ end
@@ -0,0 +1,4 @@
1
+
2
+ class Category < ActiveRecord::Base
3
+ has_many :posts, inverse_of: :category
4
+ end
@@ -0,0 +1,6 @@
1
+
2
+ class GeoZone < ActiveRecord::Base
3
+ has_many :users, foreign_key: 'zone_id', inverse_of: :zone
4
+ has_many :subzones, class_name: 'GeoZone', foreign_key: 'parent_zone_id', dependent: :destroy
5
+ belongs_to :parent_zone, class_name: 'GeoZone'
6
+ end
@@ -0,0 +1,5 @@
1
+
2
+ class Group < ActiveRecord::Base
3
+ has_many :group_users, inverse_of: :group
4
+ has_many :users, through: :group_users, inverse_of: :groups
5
+ end
@@ -0,0 +1,5 @@
1
+
2
+ class GroupUser < ActiveRecord::Base
3
+ belongs_to :group, inverse_of: :group_users
4
+ belongs_to :user, inverse_of: :group_users
5
+ end
@@ -0,0 +1,24 @@
1
+
2
+ class Post < ActiveRecord::Base
3
+ belongs_to :author, foreign_key: 'author_id', class_name: 'User', inverse_of: :posts
4
+ belongs_to :category, inverse_of: :posts
5
+ has_many :post_tags
6
+ has_many :tags, through: :post_tags, inverse_of: :posts
7
+
8
+ def add_tag(tag)
9
+ begin
10
+ PostTag.create!(post: self, tag: tag)
11
+ rescue ActiveRecord::RecordNotUnique
12
+ end
13
+ end
14
+
15
+ def add_tag_by_name(tag_name)
16
+ begin
17
+ new_tag = Tag.create!(name: tag_name)
18
+ rescue ActiveRecord::RecordNotUnique
19
+ new_tag = Tag.find_by(name: tag_name)
20
+ end
21
+ add_tag(new_tag)
22
+ end
23
+
24
+ end