├── .gemtest ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .ruby-gemset ├── .travis.yml ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.devtools ├── Guardfile ├── LICENSE ├── README.md ├── Rakefile ├── TODO ├── axiom-sql-generator.gemspec ├── config ├── devtools.yml ├── flay.yml ├── flog.yml ├── mutant.yml ├── reek.yml ├── rubocop.yml └── yardstick.yml ├── lib ├── axiom-sql-generator.rb └── axiom │ └── sql │ ├── generator.rb │ └── generator │ ├── attribute.rb │ ├── core_ext │ ├── date.rb │ └── date_time.rb │ ├── direction.rb │ ├── function.rb │ ├── function │ ├── aggregate.rb │ ├── connective.rb │ ├── numeric.rb │ ├── predicate.rb │ ├── proposition.rb │ └── string.rb │ ├── identifier.rb │ ├── literal.rb │ ├── relation.rb │ ├── relation │ ├── base.rb │ ├── binary.rb │ ├── insertion.rb │ ├── materialized.rb │ ├── set.rb │ └── unary.rb │ ├── version.rb │ └── visitor.rb └── spec ├── rcov.opts ├── shared └── generated_sql_behavior.rb ├── spec_helper.rb ├── support └── config_alias.rb └── unit ├── axiom └── sql │ └── generator │ ├── attribute │ └── visit_axiom_attribute_spec.rb │ ├── class_methods │ └── parenthesize_spec.rb │ ├── direction │ ├── visit_axiom_relation_operation_sorted_ascending_spec.rb │ └── visit_axiom_relation_operation_sorted_descending_spec.rb │ ├── function │ ├── aggregate │ │ ├── visit_axiom_aggregate_count_spec.rb │ │ ├── visit_axiom_aggregate_maximum_spec.rb │ │ ├── visit_axiom_aggregate_mean_spec.rb │ │ ├── visit_axiom_aggregate_minimum_spec.rb │ │ ├── visit_axiom_aggregate_standard_deviation_spec.rb │ │ ├── visit_axiom_aggregate_sum_spec.rb │ │ └── visit_axiom_aggregate_variance_spec.rb │ ├── connective │ │ ├── visit_axiom_function_connective_conjunction_spec.rb │ │ ├── visit_axiom_function_connective_disjunction_spec.rb │ │ └── visit_axiom_function_connective_negation_spec.rb │ ├── numeric │ │ ├── visit_axiom_function_numeric_absolute_spec.rb │ │ ├── visit_axiom_function_numeric_addition_spec.rb │ │ ├── visit_axiom_function_numeric_division_spec.rb │ │ ├── visit_axiom_function_numeric_exponentiation_spec.rb │ │ ├── visit_axiom_function_numeric_modulo_spec.rb │ │ ├── visit_axiom_function_numeric_multiplication_spec.rb │ │ ├── visit_axiom_function_numeric_square_root_spec.rb │ │ ├── visit_axiom_function_numeric_subtraction_spec.rb │ │ ├── visit_axiom_function_numeric_unary_minus_spec.rb │ │ └── visit_axiom_function_numeric_unary_plus_spec.rb │ ├── predicate │ │ ├── visit_axiom_function_predicate_equality_spec.rb │ │ ├── visit_axiom_function_predicate_exclusion_spec.rb │ │ ├── visit_axiom_function_predicate_greater_than_or_equal_to_spec.rb │ │ ├── visit_axiom_function_predicate_greater_than_spec.rb │ │ ├── visit_axiom_function_predicate_inclusion_spec.rb │ │ ├── visit_axiom_function_predicate_inequality_spec.rb │ │ ├── visit_axiom_function_predicate_less_than_or_equal_to_spec.rb │ │ └── visit_axiom_function_predicate_less_than_spec.rb │ ├── proposition │ │ ├── visit_axiom_function_proposition_contradiction_spec.rb │ │ └── visit_axiom_function_proposition_tautology_spec.rb │ └── string │ │ └── visit_axiom_function_string_length_spec.rb │ ├── identifier │ └── visit_identifier_spec.rb │ ├── literal │ ├── class_methods │ │ └── dup_frozen_spec.rb │ ├── visit_class_spec.rb │ ├── visit_date_spec.rb │ ├── visit_date_time_spec.rb │ ├── visit_enumerable_spec.rb │ ├── visit_false_class_spec.rb │ ├── visit_nil_class_spec.rb │ ├── visit_numeric_spec.rb │ ├── visit_string_spec.rb │ ├── visit_time_spec.rb │ └── visit_true_class_spec.rb │ ├── relation │ ├── binary │ │ ├── base │ │ │ ├── to_subquery_spec.rb │ │ │ └── visit_axiom_relation_base_spec.rb │ │ ├── to_s_spec.rb │ │ ├── to_subquery_spec.rb │ │ ├── visit_axiom_algebra_join_spec.rb │ │ └── visit_axiom_algebra_product_spec.rb │ ├── class_methods │ │ └── visit_spec.rb │ ├── insertion │ │ ├── to_subquery_spec.rb │ │ └── visit_axiom_relation_operation_insertion_spec.rb │ ├── materialized │ │ ├── visit_axiom_relation_materialized_spec.rb │ │ └── visited_spec.rb │ ├── name_spec.rb │ ├── set │ │ ├── class_methods │ │ │ └── normalize_operand_headers_spec.rb │ │ ├── to_s_spec.rb │ │ ├── to_subquery_spec.rb │ │ ├── visit_axiom_algebra_difference_spec.rb │ │ ├── visit_axiom_algebra_intersection_spec.rb │ │ └── visit_axiom_algebra_union_spec.rb │ ├── to_s_spec.rb │ ├── to_sql_spec.rb │ ├── to_subquery_spec.rb │ ├── unary │ │ ├── to_s_spec.rb │ │ ├── to_subquery_spec.rb │ │ ├── visit_axiom_algebra_extension_spec.rb │ │ ├── visit_axiom_algebra_projection_spec.rb │ │ ├── visit_axiom_algebra_rename_spec.rb │ │ ├── visit_axiom_algebra_restriction_spec.rb │ │ ├── visit_axiom_algebra_summarization_spec.rb │ │ ├── visit_axiom_relation_base_spec.rb │ │ ├── visit_axiom_relation_operation_limit_spec.rb │ │ ├── visit_axiom_relation_operation_offset_spec.rb │ │ ├── visit_axiom_relation_operation_reverse_spec.rb │ │ └── visit_axiom_relation_operation_sorted_spec.rb │ ├── visit_spec.rb │ └── visited_spec.rb │ └── visitor │ ├── class_methods │ └── handler_for_spec.rb │ ├── visit_spec.rb │ └── visited_spec.rb ├── date └── iso8601_spec.rb └── date_time └── iso8601_spec.rb /.gemtest: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkubb/axiom-sql-generator/a311dcac71c98ee286aff80d22be65463df4b34a/.gemtest -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## MAC OS 2 | .DS_Store 3 | 4 | ## TEXTMATE 5 | *.tmproj 6 | tmtags 7 | 8 | ## EMACS 9 | *~ 10 | \#* 11 | .\#* 12 | 13 | ## VIM 14 | *.swp 15 | 16 | ## Rubinius 17 | *.rbc 18 | .rbx 19 | 20 | ## PROJECT::GENERAL 21 | *.gem 22 | coverage 23 | profiling 24 | turbulence 25 | rdoc 26 | pkg 27 | tmp 28 | doc 29 | log 30 | .yardoc 31 | measurements 32 | 33 | ## BUNDLER 34 | .bundle 35 | Gemfile.lock 36 | 37 | ## PROJECT::SPECIFIC 38 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format progress 3 | --profile 4 | --order random 5 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Includes: 3 | - 'Gemfile' 4 | Excludes: 5 | - 'Gemfile.devtools' 6 | - 'vendor/**' 7 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | axiom-sql-generator 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | before_install: gem install bundler 3 | bundler_args: --without yard guard benchmarks 4 | script: "bundle exec rake ci:metrics" 5 | rvm: 6 | - 1.9.3 7 | - 2.0.0 8 | - 2.1.0 9 | - ruby-head 10 | - rbx 11 | matrix: 12 | include: 13 | - rvm: jruby-19mode 14 | env: JRUBY_OPTS="$JRUBY_OPTS --debug" # for simplecov 15 | - rvm: jruby-20mode 16 | env: JRUBY_OPTS="$JRUBY_OPTS --debug" # for simplecov 17 | - rvm: jruby-21mode 18 | env: JRUBY_OPTS="$JRUBY_OPTS --debug" # for simplecov 19 | - rvm: jruby-head 20 | env: JRUBY_OPTS="$JRUBY_OPTS --debug" # for simplecov 21 | allow_failures: 22 | - rvm: 2.1.0 # buggy runtime 23 | fast_finish: true 24 | notifications: 25 | irc: 26 | channels: 27 | - irc.freenode.org#rom-rb 28 | on_success: never 29 | on_failure: change 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | * If you want your code merged into the mainline, please discuss the proposed changes with me before doing any work on it. This library is still in early development, and the direction it is going may not always be clear. Some features may not be appropriate yet, may need to be deferred until later when the foundation for them is laid, or may be more applicable in a plugin. 4 | * Fork the project. 5 | * Make your feature addition or bug fix. 6 | * Follow this [style guide](https://github.com/dkubb/styleguide). 7 | * Add specs for it. This is important so I don't break it in a future version unintentionally. Tests must cover all branches within the code, and code must be fully covered. 8 | * Commit, do not mess with Rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull) 9 | * Run "rake ci". This must pass and not show any regressions in the metrics for the code to be merged. 10 | * Send me a pull request. Bonus points for topic branches. 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | platform :rbx do 8 | gem 'rubysl-bigdecimal', '~> 2.0.2' 9 | end 10 | 11 | group :development, :test do 12 | gem 'devtools', git: 'https://github.com/rom-rb/devtools.git' 13 | end 14 | 15 | eval_gemfile 'Gemfile.devtools' 16 | -------------------------------------------------------------------------------- /Gemfile.devtools: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | group :development do 4 | gem 'rake', '~> 10.1.0' 5 | gem 'rspec', '~> 2.14.1' 6 | gem 'yard', '~> 0.8.7' 7 | 8 | platform :rbx do 9 | gem 'rubysl-singleton', '~> 2.0.0' 10 | end 11 | end 12 | 13 | group :yard do 14 | gem 'kramdown', '~> 1.3.0' 15 | end 16 | 17 | group :guard do 18 | gem 'guard', '~> 2.3.0' 19 | gem 'guard-bundler', '~> 2.0.0' 20 | gem 'guard-rspec', '~> 4.2.0' 21 | gem 'guard-rubocop', '~> 1.0.0' 22 | 23 | # file system change event handling 24 | gem 'listen', '~> 2.4.0' 25 | gem 'rb-fchange', '~> 0.0.6', require: false 26 | gem 'rb-fsevent', '~> 0.9.3', require: false 27 | gem 'rb-inotify', '~> 0.9.0', require: false 28 | 29 | # notification handling 30 | gem 'libnotify', '~> 0.8.0', require: false 31 | gem 'rb-notifu', '~> 0.0.4', require: false 32 | gem 'terminal-notifier-guard', '~> 1.5.3', require: false 33 | end 34 | 35 | group :metrics do 36 | gem 'coveralls', '~> 0.7.0' 37 | gem 'flay', '~> 2.4.0' 38 | gem 'flog', '~> 4.2.0' 39 | gem 'reek', '~> 1.3.2' 40 | gem 'rubocop', '~> 0.16.0' 41 | gem 'simplecov', '~> 0.8.2' 42 | gem 'yardstick', '~> 0.9.9' 43 | 44 | platforms :mri do 45 | gem 'mutant', '~> 0.3.4' 46 | end 47 | 48 | platforms :ruby_19, :ruby_20 do 49 | gem 'yard-spellcheck', '~> 0.1.5' 50 | end 51 | 52 | platform :rbx do 53 | gem 'json', '~> 1.8.1' 54 | gem 'racc', '~> 1.4' 55 | gem 'rubysl-logger', '~> 2.0.0' 56 | gem 'rubysl-open-uri', '~> 2.0.0' 57 | gem 'rubysl-prettyprint', '~> 2.0.2' 58 | end 59 | end 60 | 61 | group :benchmarks do 62 | gem 'rbench', '~> 0.2.3' 63 | end 64 | 65 | platform :jruby do 66 | group :jruby do 67 | gem 'jruby-openssl', '~> 0.8.5' 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | guard :bundler do 4 | watch('Gemfile') 5 | end 6 | 7 | guard :rspec, :cli => File.read('.rspec').split.join(' '), :keep_failed => false do 8 | # run all specs if configuration is modified 9 | watch('Guardfile') { 'spec' } 10 | watch('Gemfile.lock') { 'spec' } 11 | watch('spec/spec_helper.rb') { 'spec' } 12 | 13 | # run all specs if supporting files files are modified 14 | watch(%r{\Aspec/(?:lib|support|shared)/.+\.rb\z}) { 'spec' } 15 | 16 | # run unit specs if associated lib code is modified 17 | watch(%r{\Alib/(.+)\.rb\z}) { |m| Dir["spec/unit/#{m[1]}"] } 18 | watch(%r{\Alib/(.+)/support/(.+)\.rb\z}) { |m| Dir["spec/unit/#{m[1]}/#{m[2]}"] } 19 | watch("lib/#{File.basename(File.expand_path('../', __FILE__))}.rb") { 'spec' } 20 | 21 | # run a spec if it is modified 22 | watch(%r{\Aspec/(?:unit|integration)/.+_spec\.rb\z}) 23 | end 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2013 Dan Kubb 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # axiom-sql-generator 2 | 3 | Relational algebra SQL generator 4 | 5 | [![Gem Version](https://badge.fury.io/rb/axiom-sql-generator.png)][gem] 6 | [![Build Status](https://secure.travis-ci.org/dkubb/axiom-sql-generator.png?branch=master)][travis] 7 | [![Dependency Status](https://gemnasium.com/dkubb/axiom-sql-generator.png)][gemnasium] 8 | [![Code Climate](https://codeclimate.com/github/dkubb/axiom-sql-generator.png)][codeclimate] 9 | [![Coverage Status](https://coveralls.io/repos/dkubb/axiom-sql-generator/badge.png?branch=master)][coveralls] 10 | 11 | [gem]: https://rubygems.org/gems/axiom-sql-generator 12 | [travis]: https://travis-ci.org/dkubb/axiom-sql-generator 13 | [gemnasium]: https://gemnasium.com/dkubb/axiom-sql-generator 14 | [codeclimate]: https://codeclimate.com/github/dkubb/axiom-sql-generator 15 | [coveralls]: https://coveralls.io/r/dkubb/axiom-sql-generator 16 | 17 | ## Usage 18 | 19 | ```ruby 20 | # visit every node in the relation AST 21 | generator = Axiom::SQL::Generator::Relation.visit(relation) 22 | 23 | # generate an SQL string 24 | sql = generator.to_sql 25 | 26 | # generate an SQL subquery string 27 | subquery_sql = generator.to_subquery 28 | ``` 29 | 30 | ## Description 31 | 32 | The purpose of this gem is to produce valid SQL from a [axiom](https://github.com/dkubb/axiom) relation. A relation is a representation of a query constructed using relational algebra organized into an AST. Each node in the AST corresponds to an operation defined in the algebra. 33 | 34 | The SQL produced has been verified and tested against [PostgreSQL](http://www.postgresql.org/) 9.0.4. Dialects for [MySQL](http://www.mysql.com/), [SQLite](http://www.sqlite.org/), [Oracle](http://www.oracle.com/) and [SQL Server](http://www.microsoft.com/sqlserver/) are planned. 35 | 36 | ## Contributing 37 | 38 | See [CONTRIBUTING.md](CONTRIBUTING.md) for details. 39 | 40 | ## Copyright 41 | 42 | Copyright © 2010-2013 Dan Kubb. See LICENSE for details. 43 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'devtools' 4 | 5 | Devtools.init_rake_tasks 6 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * Replace some of the conditional function with polymorphism, especially 2 | where there is special case function for certain types of generators 3 | within other generators. 4 | * Replace Equality/Inequality with classes that handle all of the 5 | special cases. 6 | * Use different classes for inclusive/exclusive Range literals so that 7 | they can be serialized to SQL differently. 8 | * Create Axiom::Literal::Range proxy object w/factory method 9 | * Create Axiom::Literal::Range::Inclusive 10 | * Create Axiom::Literal::Range::Exclusive 11 | 12 | * Handle cases where an Inequality/Exclusion predicate is used (or a 13 | Negation wrapping an Equality/Inclusion) on an *optional* attribute. 14 | * Add "OR attribute IS NULL" to the statement to ensure cases when 15 | the value is NULL still matches. 16 | 17 | * Handle case where Inclusion/Exclusion predicate enumerable contains a nil value. 18 | * Remove the nil value from the enumerable. 19 | * If the attribute is *optional*, change query so that "OR attribute IS * NULL" 20 | is added to the output statement. 21 | 22 | * Handle cases where WHERE clause does and does not reference a function 23 | in the SELECT list. 24 | * It should collapse the inner subquery when there is no reference, and 25 | keep the subquery when it is referenced. 26 | * This will likely apply to projection and rename operators too. 27 | 28 | * When Restriction wraps a Summarization, and it would normally wrap the 29 | statement and apply a WHERE clause, it should apply the clause using 30 | HAVING instead. 31 | 32 | * Handle case where Extension does not reference a column in the 33 | wrapped Summarization or Rename. It should be safe to collapse the query 34 | in those cases. 35 | -------------------------------------------------------------------------------- /axiom-sql-generator.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require File.expand_path('../lib/axiom/sql/generator/version', __FILE__) 4 | 5 | Gem::Specification.new do |gem| 6 | gem.name = 'axiom-sql-generator' 7 | gem.version = Axiom::SQL::Generator::VERSION.dup 8 | gem.authors = ['Dan Kubb'] 9 | gem.email = 'dan.kubb@gmail.com' 10 | gem.description = 'Generate SQL from a axiom relation' 11 | gem.summary = 'Relational algebra SQL generator' 12 | gem.homepage = 'https://github.com/dkubb/axiom-sql-generator' 13 | gem.license = 'MIT' 14 | 15 | gem.require_paths = %w[lib] 16 | gem.files = `git ls-files`.split($/) 17 | gem.test_files = `git ls-files -- spec/unit`.split($/) 18 | gem.extra_rdoc_files = %w[LICENSE README.md CONTRIBUTING.md TODO] 19 | 20 | gem.add_runtime_dependency('axiom', '~> 0.2.0') 21 | 22 | gem.add_development_dependency('bundler', '~> 1.5', '>= 1.5.2') 23 | end 24 | -------------------------------------------------------------------------------- /config/devtools.yml: -------------------------------------------------------------------------------- 1 | --- 2 | unit_test_timeout: 1.0 3 | -------------------------------------------------------------------------------- /config/flay.yml: -------------------------------------------------------------------------------- 1 | --- 2 | threshold: 63 3 | total_score: 399 4 | -------------------------------------------------------------------------------- /config/flog.yml: -------------------------------------------------------------------------------- 1 | --- 2 | threshold: 12.0 3 | -------------------------------------------------------------------------------- /config/mutant.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: axiom-sql-generator 3 | namespace: Axiom::SQL::Generator 4 | -------------------------------------------------------------------------------- /config/reek.yml: -------------------------------------------------------------------------------- 1 | --- 2 | Attribute: 3 | enabled: true 4 | exclude: 5 | - Axiom::SQL::Generator::Relation 6 | BooleanParameter: 7 | enabled: true 8 | exclude: [] 9 | ClassVariable: 10 | enabled: true 11 | exclude: [] 12 | ControlParameter: 13 | enabled: true 14 | exclude: [] 15 | DataClump: 16 | enabled: true 17 | exclude: 18 | - Axiom::SQL::Generator::Function 19 | max_copies: 2 20 | min_clump_size: 2 21 | DuplicateMethodCall: 22 | enabled: true 23 | exclude: [] 24 | max_calls: 1 25 | allow_calls: [] 26 | FeatureEnvy: 27 | enabled: true 28 | exclude: 29 | - Axiom::SQL::Generator::Function::Predicate#new_from_enumerable_predicate 30 | - Axiom::SQL::Generator::Function::Predicate#optional? 31 | - Axiom::SQL::Generator::Function::Predicate#visit_axiom_function_predicate_inequality 32 | - Axiom::SQL::Generator::Identifier#visit_identifier 33 | - Axiom::SQL::Generator::Identifier#visit_identifier 34 | - Axiom::SQL::Generator::Literal#visit_numeric 35 | - Axiom::SQL::Generator::Literal#visit_string 36 | - Axiom::SQL::Generator::Literal#visit_string 37 | - Axiom::SQL::Generator::Relation#column_list_for 38 | IrresponsibleModule: 39 | enabled: true 40 | exclude: [] 41 | LongParameterList: 42 | enabled: true 43 | exclude: 44 | - Axiom::SQL::Generator::Function::Predicate#new_from_enumerable_predicate 45 | max_params: 2 46 | overrides: 47 | initialize: 48 | max_params: 3 49 | LongYieldList: 50 | enabled: true 51 | exclude: [] 52 | max_params: 2 53 | NestedIterators: 54 | enabled: true 55 | exclude: 56 | - Axiom::SQL::Generator::Relation::Materialized#visit_axiom_relation_materialized 57 | - Axiom::SQL::Generator::Visitor#self.handlers 58 | max_allowed_nesting: 1 59 | ignore_iterators: [] 60 | NilCheck: 61 | enabled: true 62 | exclude: 63 | - Axiom::SQL::Generator::Function::Predicate#inequality_sql 64 | - Axiom::SQL::Generator::Function::Predicate#visit_axiom_function_predicate_equality 65 | RepeatedConditional: 66 | enabled: true 67 | exclude: 68 | - Axiom::SQL::Generator::Relation 69 | max_ifs: 1 70 | TooManyInstanceVariables: 71 | enabled: true 72 | exclude: 73 | - Axiom::SQL::Generator::Relation::Binary 74 | - Axiom::SQL::Generator::Relation::Unary 75 | max_instance_variables: 3 76 | TooManyMethods: 77 | enabled: true 78 | exclude: 79 | - Axiom::SQL::Generator::Relation 80 | - Axiom::SQL::Generator::Relation::Unary 81 | max_methods: 10 82 | TooManyStatements: 83 | enabled: true 84 | exclude: 85 | - Date#iso8601 86 | - DateTime#iso8601_timediv 87 | - Axiom::SQL::Generator::Function::Predicate#exclusive_range_exclusion_sql 88 | - Axiom::SQL::Generator::Function::Predicate#exclusive_range_inclusion_sql 89 | - Axiom::SQL::Generator::Function::Predicate#inequality_expressions 90 | - Axiom::SQL::Generator::Function::Predicate#visit_axiom_function_predicate_inequality 91 | - Axiom::SQL::Generator::Literal#visit_class 92 | - Axiom::SQL::Generator::Relation#column_list_for 93 | - Axiom::SQL::Generator::Relation#columns_for 94 | - Axiom::SQL::Generator::Relation#implicit_columns 95 | - Axiom::SQL::Generator::Relation#self.visit 96 | - Axiom::SQL::Generator::Relation::Binary#set_operands 97 | - Axiom::SQL::Generator::Relation::Binary#visit_axiom_algebra_join 98 | - Axiom::SQL::Generator::Relation::Binary#visit_axiom_algebra_product 99 | - Axiom::SQL::Generator::Relation::Insertion#visit_axiom_relation_operation_insertion 100 | - Axiom::SQL::Generator::Relation::Materialized#generate_sql 101 | - Axiom::SQL::Generator::Relation::Set#self.normalize_operand_headers 102 | - Axiom::SQL::Generator::Relation::Set#visit_axiom_algebra_difference 103 | - Axiom::SQL::Generator::Relation::Set#visit_axiom_algebra_intersection 104 | - Axiom::SQL::Generator::Relation::Set#visit_axiom_algebra_union 105 | - Axiom::SQL::Generator::Relation::Unary#reset_query_state 106 | - Axiom::SQL::Generator::Relation::Unary#subquery_for 107 | - Axiom::SQL::Generator::Relation::Unary#summarize_per 108 | - Axiom::SQL::Generator::Relation::Unary#visit_axiom_algebra_extension 109 | - Axiom::SQL::Generator::Relation::Unary#visit_axiom_algebra_projection 110 | - Axiom::SQL::Generator::Relation::Unary#visit_axiom_algebra_rename 111 | - Axiom::SQL::Generator::Relation::Unary#visit_axiom_algebra_restriction 112 | - Axiom::SQL::Generator::Relation::Unary#visit_axiom_algebra_summarization 113 | - Axiom::SQL::Generator::Relation::Unary#visit_axiom_relation_base 114 | - Axiom::SQL::Generator::Relation::Unary#visit_axiom_relation_operation_binary 115 | - Axiom::SQL::Generator::Relation::Unary#visit_axiom_relation_operation_limit 116 | - Axiom::SQL::Generator::Relation::Unary#visit_axiom_relation_operation_offset 117 | - Axiom::SQL::Generator::Relation::Unary#visit_axiom_relation_operation_sorted 118 | - Axiom::SQL::Generator::Visitor#self.method_for 119 | - each 120 | max_statements: 2 121 | UncommunicativeMethodName: 122 | enabled: true 123 | exclude: 124 | - Date#iso8601 125 | - DateTime#iso8601 126 | reject: 127 | - !ruby/regexp /^[a-z]$/ 128 | - !ruby/regexp /[0-9]$/ 129 | - !ruby/regexp /[A-Z]/ 130 | accept: [] 131 | UncommunicativeModuleName: 132 | enabled: true 133 | exclude: [] 134 | reject: 135 | - !ruby/regexp /^.$/ 136 | - !ruby/regexp /[0-9]$/ 137 | accept: [] 138 | UncommunicativeParameterName: 139 | enabled: true 140 | exclude: [] 141 | reject: 142 | - !ruby/regexp /^.$/ 143 | - !ruby/regexp /[0-9]$/ 144 | - !ruby/regexp /[A-Z]/ 145 | accept: [] 146 | UncommunicativeVariableName: 147 | enabled: true 148 | exclude: [] 149 | reject: 150 | - !ruby/regexp /^.$/ 151 | - !ruby/regexp /[0-9]$/ 152 | - !ruby/regexp /[A-Z]/ 153 | accept: [] 154 | UnusedParameters: 155 | enabled: true 156 | exclude: [] 157 | UtilityFunction: 158 | enabled: true 159 | exclude: 160 | - Axiom::SQL::Generator::Function::Predicate#new_from_enumerable_predicate 161 | - Axiom::SQL::Generator::Function::Predicate#optional? 162 | - Axiom::SQL::Generator::Identifier#visit_identifier 163 | - Axiom::SQL::Generator::Literal#visit_numeric 164 | - Axiom::SQL::Generator::Literal#visit_string 165 | max_helper_calls: 0 166 | -------------------------------------------------------------------------------- /config/rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: ../.rubocop.yml 2 | 3 | # Avoid parameter lists longer than five parameters. 4 | ParameterLists: 5 | Max: 3 6 | CountKeywordArgs: true 7 | 8 | # Avoid more than `Max` levels of nesting. 9 | BlockNesting: 10 | Max: 3 11 | 12 | # Align with the style guide. 13 | CollectionMethods: 14 | PreferredMethods: 15 | collect: 'map' 16 | inject: 'reduce' 17 | find: 'detect' 18 | find_all: 'select' 19 | 20 | # Do not force public/protected/private keyword to be indented at the same 21 | # level as the def keyword. My personal preference is to outdent these keywords 22 | # because I think when scanning code it makes it easier to identify the 23 | # sections of code and visually separate them. When the keyword is at the same 24 | # level I think it sort of blends in with the def keywords and makes it harder 25 | # to scan the code and see where the sections are. 26 | AccessModifierIndentation: 27 | Enabled: false 28 | 29 | # Limit line length 30 | LineLength: 31 | Max: 392 # TODO: lower to 79 32 | 33 | # Disable documentation checking until a class needs to be documented once 34 | Documentation: 35 | Enabled: false 36 | 37 | # Do not always use &&/|| instead of and/or. 38 | AndOr: 39 | Enabled: false 40 | 41 | # Do not favor modifier if/unless usage when you have a single-line body 42 | IfUnlessModifier: 43 | Enabled: false 44 | 45 | # Allow case equality operator (in limited use within the specs) 46 | CaseEquality: 47 | Enabled: false 48 | 49 | # Constants do not always have to use SCREAMING_SNAKE_CASE 50 | ConstantName: 51 | Enabled: false 52 | 53 | # Not all trivial readers/writers can be defined with attr_* methods 54 | TrivialAccessors: 55 | Enabled: false 56 | 57 | # Allow empty lines around body 58 | EmptyLinesAroundBody: 59 | Enabled: false 60 | -------------------------------------------------------------------------------- /config/yardstick.yml: -------------------------------------------------------------------------------- 1 | --- 2 | threshold: 100 3 | -------------------------------------------------------------------------------- /lib/axiom-sql-generator.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'axiom/sql/generator' 4 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'date' 4 | require 'time' 5 | 6 | require 'axiom' 7 | 8 | require 'axiom/sql/generator/core_ext/date' 9 | require 'axiom/sql/generator/core_ext/date_time' 10 | 11 | require 'axiom/sql/generator/visitor' 12 | 13 | require 'axiom/sql/generator/identifier' 14 | require 'axiom/sql/generator/attribute' 15 | require 'axiom/sql/generator/direction' 16 | require 'axiom/sql/generator/literal' 17 | 18 | require 'axiom/sql/generator/function' 19 | require 'axiom/sql/generator/function/aggregate' 20 | require 'axiom/sql/generator/function/connective' 21 | require 'axiom/sql/generator/function/numeric' 22 | require 'axiom/sql/generator/function/predicate' 23 | require 'axiom/sql/generator/function/proposition' 24 | require 'axiom/sql/generator/function/string' 25 | 26 | require 'axiom/sql/generator/relation' 27 | require 'axiom/sql/generator/relation/unary' 28 | require 'axiom/sql/generator/relation/base' 29 | require 'axiom/sql/generator/relation/binary' 30 | require 'axiom/sql/generator/relation/materialized' 31 | require 'axiom/sql/generator/relation/set' 32 | 33 | require 'axiom/sql/generator/relation/insertion' 34 | 35 | require 'axiom/sql/generator/version' 36 | 37 | module Axiom 38 | module SQL 39 | module Generator 40 | 41 | # Raised when an invalid relation is visited 42 | class InvalidRelationError < StandardError; end 43 | 44 | LEFT_PARENTHESIS = '('.freeze 45 | RIGHT_PARENTHESIS = ')'.freeze 46 | 47 | # Return a parenthesized SQL statement (inline modification) 48 | # 49 | # @param [#to_s] sql 50 | # 51 | # @return [#to_s] 52 | # same instance as sql 53 | # 54 | # @api private 55 | def self.parenthesize!(sql) 56 | sql.insert(0, LEFT_PARENTHESIS) << RIGHT_PARENTHESIS 57 | end 58 | 59 | end # module Generator 60 | end # module SQL 61 | end # module Axiom 62 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/attribute.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | 7 | # Generates an SQL statement for an attribute 8 | module Attribute 9 | include Identifier 10 | 11 | # Visit an Attribute 12 | # 13 | # @param [Attribute] attribute 14 | # 15 | # @return [#to_s] 16 | # 17 | # @api private 18 | def visit_axiom_attribute(attribute) 19 | visit_identifier(attribute.name) 20 | end 21 | 22 | end # module Attribute 23 | end # module Generator 24 | end # module SQL 25 | end # module Axiom 26 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/core_ext/date.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # Extend Date with methods available in ruby 1.9 4 | class Date 5 | 6 | ISO_8601_FORMAT = '%F'.freeze 7 | 8 | # Return the Date in ISO8601 date format 9 | # 10 | # @return [#to_s] 11 | # 12 | # @todo Remove once backports adds this method 13 | # 14 | # @api private 15 | def iso8601 16 | date = frozen? ? dup : self 17 | date.strftime(ISO_8601_FORMAT) 18 | end 19 | 20 | end # class Date 21 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/core_ext/date_time.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # Extend DateTime with methods available in ruby 1.9 4 | class DateTime 5 | 6 | # TODO: remove the rbx guard when fractional seconds are handled properly in 1.9 mode 7 | SEC_FRACTION_MULTIPLIER = RUBY_VERSION < '1.9' ? 60 * 60 * 24 : 1 8 | 9 | # Return the DateTime in ISO8601 date-time format 10 | # 11 | # @param [Integer] time_scale 12 | # the number of significant digits to use for fractional seconds 13 | # 14 | # @return [#to_s] 15 | # 16 | # @todo Remove once backports adds this method 17 | # 18 | # @api private 19 | def iso8601(time_scale = 0) 20 | super() + iso8601_timediv(time_scale) 21 | end 22 | 23 | private 24 | 25 | # Return the time with fraction seconds 26 | # 27 | # @param [Integer] time_scale 28 | # the number of significant digits to use for fractional seconds 29 | # 30 | # @return [#to_s] 31 | # 32 | # @api private 33 | def iso8601_timediv(time_scale) 34 | date_time = frozen? ? dup : self 35 | 36 | fractional_seconds = sprintf( 37 | '.%0*d', 38 | time_scale, 39 | date_time.sec_fraction * SEC_FRACTION_MULTIPLIER * 10**time_scale 40 | ) unless time_scale.zero? 41 | 42 | date_time.strftime("T%T#{fractional_seconds}%Z") 43 | end 44 | 45 | end # class DateTime 46 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/direction.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | 7 | # Generates an SQL statement for a direction 8 | module Direction 9 | include Attribute 10 | 11 | DESC = ' DESC'.freeze 12 | 13 | # Visit an Ascending Direction 14 | # 15 | # @param [Relation::Operation::Sorted::Ascending] direction 16 | # 17 | # @return [#to_s] 18 | # 19 | # @api private 20 | def visit_axiom_relation_operation_sorted_ascending(direction) 21 | dispatch direction.attribute 22 | end 23 | 24 | # Visit an Descending Direction 25 | # 26 | # @param [Relation::Operation::Sorted::Descending] direction 27 | # 28 | # @return [#to_s] 29 | # 30 | # @api private 31 | def visit_axiom_relation_operation_sorted_descending(direction) 32 | dispatch(direction.attribute) << DESC 33 | end 34 | 35 | end # module Direction 36 | end # module Generator 37 | end # module SQL 38 | end # module Axiom 39 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/function.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | 7 | # Generates an SQL statement for a function expression 8 | module Function 9 | include Attribute, Literal 10 | 11 | private 12 | 13 | # Return the SQL for a umary prefix operation 14 | # 15 | # @param [#to_s] operator 16 | # 17 | # @param [Function::Unary] function 18 | # 19 | # @return [#to_s] 20 | # 21 | # @api private 22 | def unary_prefix_operation_sql(operator, function) 23 | "#{operator} (#{dispatch(function.operand)})" 24 | end 25 | 26 | # Return the SQL for a binary prefix operation 27 | # 28 | # @param [#to_s] operator 29 | # 30 | # @param [Function::Binary] function 31 | # 32 | # @return [#to_s] 33 | # 34 | # @api private 35 | def binary_prefix_operation_sql(operator, function) 36 | "#{operator} (#{dispatch(function.left)}, #{dispatch(function.right)})" 37 | end 38 | 39 | # Return the SQL for a binary infix operation 40 | # 41 | # @param [#to_s] operator 42 | # 43 | # @param [Function::Binary] function 44 | # 45 | # @return [#to_s] 46 | # 47 | # @api private 48 | def binary_infix_operation_sql(operator, function) 49 | "#{dispatch(function.left)} #{operator} #{dispatch(function.right)}" 50 | end 51 | 52 | end # module Function 53 | end # module Generator 54 | end # module SQL 55 | end # module Axiom 56 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/function/aggregate.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | module Function 7 | 8 | # Generates an SQL statement for an aggregate function 9 | module Aggregate 10 | include Function 11 | 12 | COUNT = 'COUNT'.freeze 13 | SUM = 'SUM'.freeze 14 | MINIMUM = 'MIN'.freeze 15 | MAXIMUM = 'MAX'.freeze 16 | MEAN = 'AVG'.freeze 17 | VARIANCE = 'VAR_POP'.freeze 18 | STANDARD_DEVIATION = 'STDDEV_POP'.freeze 19 | 20 | # Visit a count aggregate function 21 | # 22 | # @param [Axiom::Aggregate::Count] count 23 | # 24 | # @return [#to_s] 25 | # 26 | # @api private 27 | def visit_axiom_aggregate_count(count) 28 | unary_prefix_operation_sql(COUNT, count) 29 | end 30 | 31 | # Visit a sum aggregate function 32 | # 33 | # @param [Axiom::Aggregate::Sum] sum 34 | # 35 | # @return [#to_s] 36 | # 37 | # @api private 38 | def visit_axiom_aggregate_sum(sum) 39 | aggregate_function_sql(SUM, sum) 40 | end 41 | 42 | # Visit a minimum aggregate function 43 | # 44 | # @param [Axiom::Aggregate::Minimum] minimum 45 | # 46 | # @return [#to_s] 47 | # 48 | # @api private 49 | def visit_axiom_aggregate_minimum(minimum) 50 | # TODO: wrap this in a coalesce operation once the default can be made sane 51 | unary_prefix_operation_sql(MINIMUM, minimum) 52 | end 53 | 54 | # Visit a maximum aggregate function 55 | # 56 | # @param [Axiom::Aggregate::Maximum] maximum 57 | # 58 | # @return [#to_s] 59 | # 60 | # @api private 61 | def visit_axiom_aggregate_maximum(maximum) 62 | # TODO: wrap this in a coalesce operation once the default can be made sane 63 | unary_prefix_operation_sql(MAXIMUM, maximum) 64 | end 65 | 66 | # Visit a mean aggregate function 67 | # 68 | # @param [Axiom::Aggregate::Mean] mean 69 | # 70 | # @return [#to_s] 71 | # 72 | # @api private 73 | def visit_axiom_aggregate_mean(mean) 74 | unary_prefix_operation_sql(MEAN, mean) 75 | end 76 | 77 | # Visit a variance aggregate function 78 | # 79 | # @param [Axiom::Aggregate::Variance] variance 80 | # 81 | # @return [#to_s] 82 | # 83 | # @api private 84 | def visit_axiom_aggregate_variance(variance) 85 | unary_prefix_operation_sql(VARIANCE, variance) 86 | end 87 | 88 | # Visit a standard deviation aggregate function 89 | # 90 | # @param [Axiom::Aggregate::StandardDeviation] standard_deviation 91 | # 92 | # @return [#to_s] 93 | # 94 | # @api private 95 | def visit_axiom_aggregate_standard_deviation(standard_deviation) 96 | unary_prefix_operation_sql(STANDARD_DEVIATION, standard_deviation) 97 | end 98 | 99 | private 100 | 101 | # Return the SQL 102 | # 103 | # @param [#to_s] operation 104 | # 105 | # @param [Axiom::Aggregate] aggregate 106 | # 107 | # @return [#to_s] 108 | # 109 | # @api private 110 | def aggregate_function_sql(operation, aggregate) 111 | default_when_null( 112 | unary_prefix_operation_sql(operation, aggregate), 113 | aggregate.finalize(aggregate.default) 114 | ) 115 | end 116 | 117 | # Specify a default value when SQL expression evaluates to NULL 118 | # 119 | # @param [#to_s] sql 120 | # 121 | # @param [Visitable] default 122 | # 123 | # @return [#to_s] 124 | # 125 | # @api private 126 | def default_when_null(sql, default) 127 | "COALESCE (#{sql}, #{dispatch(default)})" 128 | end 129 | 130 | end # module Aggregate 131 | end # module Function 132 | end # module Generator 133 | end # module SQL 134 | end # module Axiom 135 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/function/connective.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | module Function 7 | 8 | # Generates an SQL statement for a connective 9 | module Connective 10 | include Function 11 | 12 | AND = 'AND'.freeze 13 | OR = 'OR'.freeze 14 | NOT = 'NOT'.freeze 15 | 16 | # Visit an Conjunction connective 17 | # 18 | # @param [Function::Connective::Conjunction] conjunction 19 | # 20 | # @return [#to_s] 21 | # 22 | # @api private 23 | def visit_axiom_function_connective_conjunction(conjunction) 24 | Generator.parenthesize!(binary_infix_operation_sql(AND, conjunction)) 25 | end 26 | 27 | # Visit an Disjunction connective 28 | # 29 | # @param [Function::Connective::Disjunction] disjunction 30 | # 31 | # @return [#to_s] 32 | # 33 | # @api private 34 | def visit_axiom_function_connective_disjunction(disjunction) 35 | Generator.parenthesize!(binary_infix_operation_sql(OR, disjunction)) 36 | end 37 | 38 | # Visit an Negation connective 39 | # 40 | # @param [Function::Connective::Negation] negation 41 | # 42 | # @return [#to_s] 43 | # 44 | # @api private 45 | def visit_axiom_function_connective_negation(negation) 46 | unary_prefix_operation_sql(NOT, negation) 47 | end 48 | 49 | end # module Connective 50 | end # module Function 51 | end # module Generator 52 | end # module SQL 53 | end # module Axiom 54 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/function/numeric.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | module Function 7 | 8 | # Generates an SQL statement for a numeric function 9 | module Numeric 10 | include Function 11 | 12 | ABSOLUTE = 'ABS'.freeze 13 | ADD = '+'.freeze 14 | SUBTRACT = '-'.freeze 15 | MULTIPLY = '*'.freeze 16 | DIVIDE = '/'.freeze 17 | POWER = 'POWER'.freeze 18 | MOD = 'MOD'.freeze 19 | SQUARE_ROOT = 'SQRT'.freeze 20 | 21 | # Visit an Absolute function 22 | # 23 | # @param [Function::Numeric::Absolute] absolute 24 | # 25 | # @return [#to_s] 26 | # 27 | # @api private 28 | def visit_axiom_function_numeric_absolute(absolute) 29 | unary_prefix_operation_sql(ABSOLUTE, absolute) 30 | end 31 | 32 | # Visit an Addition function 33 | # 34 | # @param [Function::Numeric::Addition] addition 35 | # 36 | # @return [#to_s] 37 | # 38 | # @api private 39 | def visit_axiom_function_numeric_addition(addition) 40 | Generator.parenthesize!(binary_infix_operation_sql(ADD, addition)) 41 | end 42 | 43 | # Visit a Division function 44 | # 45 | # @param [Function::Numeric::Division] division 46 | # 47 | # @return [#to_s] 48 | # 49 | # @api private 50 | def visit_axiom_function_numeric_division(division) 51 | Generator.parenthesize!(binary_infix_operation_sql(DIVIDE, division)) 52 | end 53 | 54 | # Visit a Exponentiation function 55 | # 56 | # @param [Function::Numeric::Exponentiation] exponentiation 57 | # 58 | # @return [#to_s] 59 | # 60 | # @api private 61 | def visit_axiom_function_numeric_exponentiation(exponentiation) 62 | binary_prefix_operation_sql(POWER, exponentiation) 63 | end 64 | 65 | # Visit a Modulo function 66 | # 67 | # @param [Function::Numeric::Modulo] modulo 68 | # 69 | # @return [#to_s] 70 | # 71 | # @api private 72 | def visit_axiom_function_numeric_modulo(modulo) 73 | binary_prefix_operation_sql(MOD, modulo) 74 | end 75 | 76 | # Visit a Multiplication function 77 | # 78 | # @param [Function::Numeric::Multiplication] multiplication 79 | # 80 | # @return [#to_s] 81 | # 82 | # @api private 83 | def visit_axiom_function_numeric_multiplication(multiplication) 84 | Generator.parenthesize!(binary_infix_operation_sql(MULTIPLY, multiplication)) 85 | end 86 | 87 | # Visit a Square Root function 88 | # 89 | # @param [Function::Numeric::SquareRoot] square_root 90 | # 91 | # @return [#to_s] 92 | # 93 | # @api private 94 | def visit_axiom_function_numeric_square_root(square_root) 95 | unary_prefix_operation_sql(SQUARE_ROOT, square_root) 96 | end 97 | 98 | # Visit an Addition function 99 | # 100 | # @param [Function::Numeric::Addition] subtraction 101 | # 102 | # @return [#to_s] 103 | # 104 | # @api private 105 | def visit_axiom_function_numeric_subtraction(subtraction) 106 | Generator.parenthesize!(binary_infix_operation_sql(SUBTRACT, subtraction)) 107 | end 108 | 109 | # Visit an Unary Minus function 110 | # 111 | # @param [Function::Numeric::UnaryMinus] unary_minus 112 | # 113 | # @return [#to_s] 114 | # 115 | # @api private 116 | def visit_axiom_function_numeric_unary_minus(unary_minus) 117 | unary_prefix_operation_sql(SUBTRACT, unary_minus) 118 | end 119 | 120 | # Visit an Unary Plus function 121 | # 122 | # @param [Function::Numeric::UnaryPlus] unary_plus 123 | # 124 | # @return [#to_s] 125 | # 126 | # @api private 127 | def visit_axiom_function_numeric_unary_plus(unary_plus) 128 | unary_prefix_operation_sql(ADD, unary_plus) 129 | end 130 | 131 | end # module Numeric 132 | end # module Function 133 | end # module Generator 134 | end # module SQL 135 | end # module Axiom 136 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/function/predicate.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | module Function 7 | 8 | # Generates an SQL statement for a predicate function 9 | module Predicate 10 | include Function 11 | 12 | EQUAL_TO = '='.freeze 13 | EQUAL_TO_NULL = 'IS'.freeze 14 | NOT_EQUAL_TO = '<>'.freeze 15 | NOT_EQUAL_TO_NULL = 'IS NOT'.freeze 16 | GREATER_THAN = '>'.freeze 17 | GREATER_THAN_OR_EQUAL_TO = '>='.freeze 18 | LESS_THAN = '<'.freeze 19 | LESS_THAN_OR_EQUAL_TO = '<='.freeze 20 | IN = 'IN'.freeze 21 | NOT_IN = 'NOT IN'.freeze 22 | BETWEEN = 'BETWEEN'.freeze 23 | NOT_BETWEEN = 'NOT BETWEEN'.freeze 24 | EMPTY_ARRAY = [].freeze 25 | 26 | # Visit an Equality predicate 27 | # 28 | # @param [Function::Predicate::Equality] equality 29 | # 30 | # @return [#to_s] 31 | # 32 | # @api private 33 | def visit_axiom_function_predicate_equality(equality) 34 | binary_infix_operation_sql(equality.right.nil? ? EQUAL_TO_NULL : EQUAL_TO, equality) 35 | end 36 | 37 | # Visit an Inequality predicate 38 | # 39 | # @param [Function::Predicate::Inequality] inequality 40 | # 41 | # @return [#to_s] 42 | # 43 | # @api private 44 | def visit_axiom_function_predicate_inequality(inequality) 45 | expressions = inequality_expressions(inequality) 46 | expressions.one? ? expressions.first : Generator.parenthesize!(expressions.join(' OR ')) 47 | end 48 | 49 | # Visit an GreaterThan predicate 50 | # 51 | # @param [Function::Predicate::GreaterThan] greater_than 52 | # 53 | # @return [#to_s] 54 | # 55 | # @api private 56 | def visit_axiom_function_predicate_greater_than(greater_than) 57 | binary_infix_operation_sql(GREATER_THAN, greater_than) 58 | end 59 | 60 | # Visit an GreaterThanOrEqualTo predicate 61 | # 62 | # @param [Function::Predicate::GreaterThanOrEqualTo] greater_than_or_equal_to 63 | # 64 | # @return [#to_s] 65 | # 66 | # @api private 67 | def visit_axiom_function_predicate_greater_than_or_equal_to(greater_than_or_equal_to) 68 | binary_infix_operation_sql(GREATER_THAN_OR_EQUAL_TO, greater_than_or_equal_to) 69 | end 70 | 71 | # Visit an LessThan predicate 72 | # 73 | # @param [Function::Predicate::LessThan] less_than 74 | # 75 | # @return [#to_s] 76 | # 77 | # @api private 78 | def visit_axiom_function_predicate_less_than(less_than) 79 | binary_infix_operation_sql(LESS_THAN, less_than) 80 | end 81 | 82 | # Visit an LessThanOrEqualTo predicate 83 | # 84 | # @param [Function::Predicate::LessThanOrEqualTo] less_than_or_equal_to 85 | # 86 | # @return [#to_s] 87 | # 88 | # @api private 89 | def visit_axiom_function_predicate_less_than_or_equal_to(less_than_or_equal_to) 90 | binary_infix_operation_sql(LESS_THAN_OR_EQUAL_TO, less_than_or_equal_to) 91 | end 92 | 93 | # Visit an Inclusion predicate 94 | # 95 | # @param [Function::Predicate::Inclusion] inclusion 96 | # 97 | # @return [#to_s] 98 | # 99 | # @api private 100 | def visit_axiom_function_predicate_inclusion(inclusion) 101 | case inclusion.right 102 | when Range then range_inclusion_sql(inclusion) 103 | when EMPTY_ARRAY then FALSE 104 | else 105 | binary_infix_operation_sql(IN, inclusion) 106 | end 107 | end 108 | 109 | # Visit an Exclusion predicate 110 | # 111 | # @param [Function::Predicate::Exclusion] exclusion 112 | # 113 | # @return [#to_s] 114 | # 115 | # @api private 116 | def visit_axiom_function_predicate_exclusion(exclusion) 117 | case exclusion.right 118 | when Range then range_exclusion_sql(exclusion) 119 | when EMPTY_ARRAY then TRUE 120 | else 121 | binary_infix_operation_sql(NOT_IN, exclusion) 122 | end 123 | end 124 | 125 | private 126 | 127 | # Return the SQL for an Inclusion using a Range 128 | # 129 | # @param [Function::Predicate::Inclusion] inclusion 130 | # 131 | # @return [#to_s] 132 | # 133 | # @api private 134 | def range_inclusion_sql(inclusion) 135 | if inclusion.right.exclude_end? 136 | exclusive_range_inclusion_sql(inclusion) 137 | else 138 | inclusive_range_sql(BETWEEN, inclusion) 139 | end 140 | end 141 | 142 | # Return the SQL for an Exclusion using a Range 143 | # 144 | # @param [Function::Predicate::Exclusion] exclusion 145 | # 146 | # @return [#to_s] 147 | # 148 | # @api private 149 | def range_exclusion_sql(exclusion) 150 | if exclusion.right.exclude_end? 151 | exclusive_range_exclusion_sql(exclusion) 152 | else 153 | inclusive_range_sql(NOT_BETWEEN, exclusion) 154 | end 155 | end 156 | 157 | # Return the SQL for an Inclusion using an exclusive Range 158 | # 159 | # @param [Function::Predicate::Inclusion] inclusion 160 | # 161 | # @return [#to_s] 162 | # 163 | # @api private 164 | def exclusive_range_inclusion_sql(inclusion) 165 | left = new_from_enumerable_predicate(Axiom::Function::Predicate::GreaterThanOrEqualTo, inclusion, :first) 166 | right = new_from_enumerable_predicate(Axiom::Function::Predicate::LessThan, inclusion, :last) 167 | dispatch left.and(right) 168 | end 169 | 170 | # Return the SQL for an Exclusion using an exclusive Range 171 | # 172 | # @param [Function::Predicate::Exclusion] exclusion 173 | # 174 | # @return [#to_s] 175 | # 176 | # @api private 177 | def exclusive_range_exclusion_sql(exclusion) 178 | left = new_from_enumerable_predicate(Axiom::Function::Predicate::LessThan, exclusion, :first) 179 | right = new_from_enumerable_predicate(Axiom::Function::Predicate::GreaterThanOrEqualTo, exclusion, :last) 180 | dispatch left.or(right) 181 | end 182 | 183 | # Instantiate a new Predicate object from an Enumerable Predicate 184 | # 185 | # @param [Class] klass 186 | # the type of predicate to create 187 | # @param [Function::Predicate::Enumerable] predicate 188 | # the enumerable predicate 189 | # @param [Symbol] method 190 | # the method to call on the right operand of the predicate 191 | # @return [Function::Predicate] 192 | # 193 | # @api private 194 | def new_from_enumerable_predicate(klass, predicate, method) 195 | klass.new(predicate.left, predicate.right.send(method)) 196 | end 197 | 198 | # Return the expressions for an inequality 199 | # 200 | # @param [Function::Predicate::Inequality] inequality 201 | # 202 | # @return [Array<#to_s>] 203 | # 204 | # @api private 205 | def inequality_expressions(inequality) 206 | expressions = [ 207 | inequality_sql(inequality), 208 | optional_is_null_sql(inequality.left), 209 | optional_is_null_sql(inequality.right), 210 | ] 211 | expressions.compact! 212 | expressions 213 | end 214 | 215 | # Return the SQL for an inequality predicate 216 | # 217 | # @param [Function::Predicate::Inequality] inequality 218 | # 219 | # @return [#to_s] 220 | # 221 | # @api private 222 | def inequality_sql(inequality) 223 | binary_infix_operation_sql(inequality.right.nil? ? NOT_EQUAL_TO_NULL : NOT_EQUAL_TO, inequality) 224 | end 225 | 226 | # Return the SQL for an operation using an inclusive Range 227 | # 228 | # @param [#to_s] operator 229 | # 230 | # @param [Function::Predicate::Enumerable] predicate 231 | # 232 | # @return [#to_s] 233 | # 234 | # @api private 235 | def inclusive_range_sql(operator, predicate) 236 | right = predicate.right 237 | "#{dispatch(predicate.left)} #{operator} #{dispatch(right.first)} AND #{dispatch(right.last)}" 238 | end 239 | 240 | # Return SQL for an Equality with a nil value for optional attributes 241 | # 242 | # @param [Attribute] attribute 243 | # 244 | # @return [#to_sql, nil] 245 | # 246 | # @api private 247 | def optional_is_null_sql(attribute) 248 | dispatch(attribute.eq(nil)) if optional?(attribute) 249 | end 250 | 251 | # Test if the object is not required 252 | # 253 | # @param [Object] operand 254 | # 255 | # @return [Boolean] 256 | # 257 | # @api private 258 | def optional?(operand) 259 | operand.respond_to?(:required?) && !operand.required? 260 | end 261 | 262 | end # module Predicate 263 | end # module Function 264 | end # module Generator 265 | end # module SQL 266 | end # module Axiom 267 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/function/proposition.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | module Function 7 | 8 | # Generates an SQL statement for a proposition function 9 | module Proposition 10 | include Function 11 | 12 | # Visit a Tautology 13 | # 14 | # @param [Function::Proposition::Tautology] _tautology 15 | # 16 | # @return [#to_s] 17 | # 18 | # @api private 19 | def visit_axiom_function_proposition_tautology(_tautology) 20 | TRUE 21 | end 22 | 23 | # Visit a Contradiction 24 | # 25 | # @param [Function::Proposition::Contradiction] _contradiction 26 | # 27 | # @return [#to_s] 28 | # 29 | # @api private 30 | def visit_axiom_function_proposition_contradiction(_contradiction) 31 | FALSE 32 | end 33 | 34 | end # module Proposition 35 | end # module Function 36 | end # module Generator 37 | end # module SQL 38 | end # module Axiom 39 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/function/string.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | module Function 7 | 8 | # Generates an SQL statement for a string function 9 | module String 10 | include Function 11 | 12 | LENGTH = 'LENGTH'.freeze 13 | 14 | # Visit a Length function 15 | # 16 | # @param [Function::String::Length] length 17 | # 18 | # @return [#to_s] 19 | # 20 | # @api private 21 | def visit_axiom_function_string_length(length) 22 | unary_prefix_operation_sql(LENGTH, length) 23 | end 24 | 25 | end # module String 26 | end # module Function 27 | end # module Generator 28 | end # module SQL 29 | end # module Axiom 30 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/identifier.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | 7 | # Generates an SQL statement for an identifier 8 | module Identifier 9 | 10 | QUOTE = '"'.freeze 11 | ESCAPED_QUOTE = '""'.freeze 12 | 13 | # Quote the identifier 14 | # 15 | # @param [#to_s] identifier 16 | # 17 | # @return [#to_s] 18 | # 19 | # @api private 20 | def visit_identifier(identifier) 21 | escaped = identifier.to_s.gsub(QUOTE, ESCAPED_QUOTE) 22 | escaped.insert(0, QUOTE) << QUOTE 23 | end 24 | 25 | end # module Identifier 26 | end # module Generator 27 | end # module SQL 28 | end # module Axiom 29 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/literal.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | 7 | # Generates an SQL statement for a literal 8 | module Literal 9 | 10 | TRUE = 'TRUE'.freeze 11 | FALSE = 'FALSE'.freeze 12 | NULL = 'NULL'.freeze 13 | QUOTE = "'".freeze 14 | ESCAPED_QUOTE = "''".freeze 15 | SEPARATOR = ', '.freeze 16 | TIME_SCALE = 9 17 | 18 | # Returns an unfrozen object 19 | # 20 | # Some objects, like Date, DateTime and Time memoize values 21 | # when serialized to a String, so when they are frozen this will 22 | # dup them and then return the unfrozen copy. 23 | # 24 | # @param [Object] object 25 | # 26 | # @return [Object] 27 | # non-frozen object 28 | # 29 | # @api private 30 | def self.dup_frozen(object) 31 | object.frozen? ? object.dup : object 32 | end 33 | 34 | # Visit an Enumerable 35 | # 36 | # @param [Enumerable] enumerable 37 | # 38 | # @return [#to_s] 39 | # 40 | # @api private 41 | def visit_enumerable(enumerable) 42 | Generator.parenthesize!( 43 | enumerable.map { |entry| dispatch entry }.join(SEPARATOR) 44 | ) 45 | end 46 | 47 | # Visit a String 48 | # 49 | # @note The string must be UTF-8 encoded 50 | # 51 | # @param [String] string 52 | # 53 | # @return [#to_s] 54 | # 55 | # @api private 56 | def visit_string(string) 57 | escaped = string.gsub(QUOTE, ESCAPED_QUOTE) 58 | escaped.insert(0, QUOTE) << QUOTE 59 | end 60 | 61 | # Visit a Numeric 62 | # 63 | # @param [Numeric] numeric 64 | # 65 | # @return [#to_s] 66 | # 67 | # @api private 68 | def visit_numeric(numeric) 69 | numeric.to_s 70 | end 71 | 72 | # Visit a Class 73 | # 74 | # @param [Class] klass 75 | # 76 | # @return [#to_s] 77 | # 78 | # @api private 79 | def visit_class(klass) 80 | name = klass.name.to_s 81 | name.empty? ? NULL : visit_string(name) 82 | end 83 | 84 | # Visit a Date and return in ISO 8601 date format 85 | # 86 | # @param [Date] date 87 | # 88 | # @return [#to_s] 89 | # 90 | # @api private 91 | def visit_date(date) 92 | dispatch date.iso8601 93 | end 94 | 95 | # Visit a DateTime and return in ISO 8601 date-time format 96 | # 97 | # Converts the DateTime to UTC format. 98 | # 99 | # @param [DateTime] date_time 100 | # 101 | # @return [#to_s] 102 | # 103 | # @api private 104 | def visit_date_time(date_time) 105 | dispatch date_time.new_offset.iso8601(TIME_SCALE) 106 | end 107 | 108 | # Visit a Time 109 | # 110 | # Converts the Time to UTC format. 111 | # 112 | # @param [Time] time 113 | # 114 | # @return [#to_s] 115 | # 116 | # @api private 117 | def visit_time(time) 118 | dispatch Literal.dup_frozen(time).utc.iso8601(TIME_SCALE) 119 | end 120 | 121 | # Visit a true value 122 | # 123 | # @param [true] _true 124 | # 125 | # @return [#to_s] 126 | # 127 | # @api private 128 | def visit_true_class(_true) 129 | TRUE 130 | end 131 | 132 | # Visit a false value 133 | # 134 | # @param [false] _false 135 | # 136 | # @return [#to_s] 137 | # 138 | # @api private 139 | def visit_false_class(_false) 140 | FALSE 141 | end 142 | 143 | # Visit a nil value 144 | # 145 | # @param [nil] _nil 146 | # 147 | # @return [#to_s] 148 | # 149 | # @api private 150 | def visit_nil_class(_nil) 151 | NULL 152 | end 153 | 154 | end # module Literal 155 | end # module Generator 156 | end # module SQL 157 | end # module Axiom 158 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/relation.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | 7 | # Abstract base class for SQL generation from a relation 8 | class Relation < Visitor 9 | extend Identifier 10 | include Attribute 11 | 12 | EMPTY_STRING = ''.freeze 13 | SEPARATOR = ', '.freeze 14 | STAR = '*'.freeze 15 | EMPTY_HASH = {}.freeze 16 | 17 | # Return the alias name 18 | # 19 | # @return [#to_s] 20 | # 21 | # @api private 22 | attr_reader :name 23 | 24 | # Factory method to instantiate the generator for the relation 25 | # 26 | # @param [Axiom::Relation] relation 27 | # 28 | # @return [Generator::Relation] 29 | # 30 | # @api private 31 | def self.visit(relation) 32 | klass = 33 | case relation 34 | when Axiom::Relation::Operation::Insertion then self::Insertion 35 | when Axiom::Relation::Operation::Set then self::Set 36 | when Axiom::Relation::Operation::Binary then self::Binary 37 | when Axiom::Relation::Operation::Unary then self::Unary 38 | when Axiom::Relation::Base then self::Base 39 | when Axiom::Relation::Materialized then self::Materialized 40 | else 41 | fail InvalidRelationError, "#{relation.class} is not a visitable relation" 42 | end 43 | klass.new.visit(relation) 44 | end 45 | 46 | # Initialize a Generator 47 | # 48 | # @return [undefined] 49 | # 50 | # @api private 51 | def initialize 52 | @sql = EMPTY_STRING 53 | @extensions = {} 54 | end 55 | 56 | # Visit an object and generate SQL from each node 57 | # 58 | # @example 59 | # generator.visit(visitable) 60 | # 61 | # @param [Visitable] visitable 62 | # A visitable object 63 | # 64 | # @return [self] 65 | # 66 | # @raise [Visitor::UnknownObject] 67 | # raised when the visitable object has no handler 68 | # 69 | # @api public 70 | def visit(visitable) 71 | @sql = dispatch(visitable).to_s.freeze 72 | freeze 73 | end 74 | 75 | # Returns the current SQL string 76 | # 77 | # @example 78 | # sql = generator.to_sql 79 | # 80 | # @return [String] 81 | # 82 | # @api public 83 | def to_sql 84 | @sql 85 | end 86 | 87 | # Return the SQL for the unary relation 88 | # 89 | # @example 90 | # sql = unary_relation.to_s 91 | # 92 | # @return [#to_s] 93 | # 94 | # @api public 95 | def to_s 96 | return EMPTY_STRING unless visited? 97 | generate_sql(query_columns) 98 | end 99 | 100 | # Return the SQL suitable for an subquery 101 | # 102 | # @return [#to_s] 103 | # 104 | # @api private 105 | def to_subquery 106 | return EMPTY_STRING unless visited? 107 | Generator.parenthesize!(generate_sql(subquery_columns)) 108 | end 109 | 110 | # Test if a visitable object has been visited 111 | # 112 | # @example 113 | # visitor.visited? # true or false 114 | # 115 | # @return [Boolean] 116 | # 117 | # @api public 118 | def visited? 119 | instance_variable_defined?(:@name) 120 | end 121 | 122 | private 123 | 124 | # Return the columns to use in a query 125 | # 126 | # @return [#to_s] 127 | # 128 | # @api private 129 | def query_columns 130 | explicit_columns 131 | end 132 | 133 | # Return the columns to use in a subquery 134 | # 135 | # @return [#to_s] 136 | # 137 | # @api private 138 | def subquery_columns 139 | implicit_columns 140 | end 141 | 142 | # Return the implicit columns for the select list 143 | # 144 | # @return [#to_s] 145 | # 146 | # @api private 147 | def implicit_columns 148 | sql = [STAR, column_list_for(@extensions)] 149 | sql.reject! { |fragment| fragment.empty? } 150 | sql.join(SEPARATOR) 151 | end 152 | 153 | # Return the explicit columns for the select list 154 | # 155 | # @return [#to_s] 156 | # 157 | # @api private 158 | def explicit_columns 159 | @distinct.to_s + column_list_for(@extensions.merge(@columns || EMPTY_HASH)) 160 | end 161 | 162 | # Return the list of columns 163 | # 164 | # @param [#map] columns 165 | # 166 | # @return [#to_s] 167 | # 168 | # @api private 169 | def column_list_for(columns) 170 | sql = columns.values_at(*@header) 171 | sql.compact! 172 | sql.join(SEPARATOR) 173 | end 174 | 175 | # Return a list of columns in a header 176 | # 177 | # @param [Axiom::Relation] relation 178 | # 179 | # @param [#[]] aliases 180 | # optional aliases for the columns 181 | # 182 | # @return [Hash] 183 | # 184 | # @api private 185 | def columns_for(relation, aliases = EMPTY_HASH) 186 | columns = {} 187 | relation.header.each do |attribute| 188 | columns[aliases.fetch(attribute, attribute)] = column_for(attribute, aliases) 189 | end 190 | columns 191 | end 192 | 193 | # Return the column for an attribute 194 | # 195 | # @param [Attribute] attribute 196 | # 197 | # @param [#[]] aliases 198 | # aliases for the columns 199 | # 200 | # @return [#to_s] 201 | # 202 | # @api private 203 | def column_for(attribute, aliases) 204 | if aliases.key?(attribute) 205 | alias_for(attribute, aliases[attribute]) 206 | else 207 | dispatch(attribute) 208 | end 209 | end 210 | 211 | # Return the column alias for an attribute 212 | # 213 | # @param [#to_s] attribute 214 | # 215 | # @param [Attribute, nil] alias_attribute 216 | # attribute to use for the alias 217 | # 218 | # @return [#to_s] 219 | # 220 | # @api private 221 | def alias_for(attribute, alias_attribute) 222 | "#{dispatch(attribute)} AS #{dispatch(alias_attribute)}" 223 | end 224 | 225 | # Add extensions for extension and summarize queries 226 | # 227 | # @param [#each] extensions 228 | # 229 | # @return [undefined] 230 | # 231 | # @api private 232 | def add_extensions(extensions) 233 | extensions.each do |attribute, function| 234 | @extensions[attribute] = alias_for(function, attribute) 235 | end 236 | end 237 | 238 | end # class Relation 239 | end # module Generator 240 | end # module SQL 241 | end # module Axiom 242 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/relation/base.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | class Relation 7 | 8 | # Generates an SQL statement for a base relation 9 | class Base < Unary; end 10 | 11 | end # class Relation 12 | end # module Generator 13 | end # module SQL 14 | end # module Axiom 15 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/relation/binary.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | class Relation 7 | 8 | # Generates an SQL statement for a Binary relation 9 | class Binary < Relation 10 | 11 | JOIN = 'NATURAL JOIN'.freeze 12 | PRODUCT = 'CROSS JOIN'.freeze 13 | LEFT_NAME = 'left'.freeze 14 | RIGHT_NAME = 'right'.freeze 15 | 16 | # Visit an Join 17 | # 18 | # @param [Algebra::Join] join 19 | # 20 | # @return [self] 21 | # 22 | # @api private 23 | def visit_axiom_algebra_join(join) 24 | @header = join.header 25 | set_operation(JOIN) 26 | set_columns(join) 27 | set_operands(join) 28 | set_name 29 | self 30 | end 31 | 32 | # Visit an Product 33 | # 34 | # @param [Algebra::Product] product 35 | # 36 | # @return [self] 37 | # 38 | # @api private 39 | def visit_axiom_algebra_product(product) 40 | @header = product.header 41 | set_operation(PRODUCT) 42 | set_columns(product) 43 | set_operands(product) 44 | set_name 45 | self 46 | end 47 | 48 | private 49 | 50 | # Generate the SQL using the supplied columns 51 | # 52 | # @param [String] columns 53 | # 54 | # @return [#to_s] 55 | # 56 | # @api private 57 | def generate_sql(columns) 58 | "SELECT #{columns} FROM #{@left.to_subquery} AS #{visit_identifier(LEFT_NAME)} #{@operation} #{@right.to_subquery} AS #{visit_identifier(RIGHT_NAME)}" 59 | end 60 | 61 | # Set the operation 62 | # 63 | # @param [#to_s] operation 64 | # 65 | # @return [undefined] 66 | # 67 | # @api private 68 | def set_operation(operation) 69 | @operation = operation 70 | end 71 | 72 | # Set the columns from the relation 73 | # 74 | # @param [Relation::Operation::Binary] relation 75 | # 76 | # @return [undefined] 77 | # 78 | # @api private 79 | def set_columns(relation) 80 | @columns = columns_for(relation) 81 | end 82 | 83 | # Set the operands from the relation 84 | # 85 | # @param [Relation::Operation::Binary] relation 86 | # 87 | # @return [undefined] 88 | # 89 | # @api private 90 | def set_operands(relation) 91 | util = self.class 92 | @left = util.visit(relation.left) 93 | @right = util.visit(relation.right) 94 | end 95 | 96 | # Set the name using the operands' name 97 | # 98 | # @return [undefined] 99 | # 100 | # @api private 101 | def set_name 102 | @name = [@left.name, @right.name].uniq.join(UNDERSCORE).freeze 103 | end 104 | 105 | # Generates an SQL statement for base relation binary operands 106 | class Base < Relation::Base 107 | 108 | # Return the SQL suitable for an subquery 109 | # 110 | # Does not parenthesize the query 111 | # 112 | # @return [#to_s] 113 | # 114 | # @api private 115 | def to_subquery 116 | return EMPTY_STRING unless visited? 117 | generate_sql 118 | end 119 | 120 | private 121 | 122 | # Generate the SQL for this base relation 123 | # 124 | # @return [#to_s] 125 | # 126 | # @api private 127 | def generate_sql(*) 128 | @from 129 | end 130 | 131 | end # class Base 132 | end # class Binary 133 | end # class Relation 134 | end # module Generator 135 | end # module SQL 136 | end # module Axiom 137 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/relation/insertion.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | class Relation 7 | 8 | # Generates an SQL statement for an insertion 9 | class Insertion < Set 10 | extend Aliasable 11 | 12 | inheritable_alias(to_subquery: :to_s) 13 | 14 | # Visit an Insertion 15 | # 16 | # @param [Relation::Operation::Insertion] insertion 17 | # 18 | # @return [self] 19 | # 20 | # @api private 21 | def visit_axiom_relation_operation_insertion(insertion) 22 | @header = insertion.header 23 | set_columns(insertion) 24 | set_operands(insertion) 25 | set_name 26 | self 27 | end 28 | 29 | private 30 | 31 | # Generate the SQL using the supplied method 32 | # 33 | # @return [#to_s] 34 | # 35 | # @api private 36 | def generate_sql(*) 37 | "INSERT INTO #{@name} #{column_list} #{@right}" 38 | end 39 | 40 | # Generate the list of columns to insert into 41 | # 42 | # @return [#to_s] 43 | # 44 | # @api private 45 | def column_list 46 | Generator.parenthesize!(column_list_for(@columns)) 47 | end 48 | 49 | # Set the name using the left operands' name 50 | # 51 | # @return [undefined] 52 | # 53 | # @api private 54 | def set_name 55 | @name = @left.name 56 | end 57 | 58 | end # class Insertion 59 | end # class Relation 60 | end # module Generator 61 | end # module SQL 62 | end # module Axiom 63 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/relation/materialized.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | class Relation 7 | 8 | # Generates an SQL statement for materialized relation 9 | class Materialized < Relation 10 | include Literal 11 | 12 | # Visit a Materialized relation 13 | # 14 | # @param [Relation::Materialized] materialized 15 | # 16 | # @return [self] 17 | # 18 | # @api private 19 | def visit_axiom_relation_materialized(materialized) 20 | @values = materialized.map do |tuple| 21 | Generator.parenthesize!( 22 | tuple.to_ary.map { |value| dispatch(value) }.join(', ') 23 | ) 24 | end 25 | self 26 | end 27 | 28 | # Test if a visitable object has been visited 29 | # 30 | # @example 31 | # visitor.visited? # true or false 32 | # 33 | # @return [Boolean] 34 | # 35 | # @api public 36 | def visited? 37 | instance_variable_defined?(:@values) 38 | end 39 | 40 | private 41 | 42 | # Generate the SQL for the materialized relation 43 | # 44 | # @return [#to_s] 45 | # 46 | # @api private 47 | def generate_sql(*) 48 | return EMPTY_STRING unless visited? 49 | if @values.empty? 50 | 'SELECT 0 LIMIT 0' # no values 51 | else 52 | "VALUES #{@values.join(', ')}" 53 | end 54 | end 55 | 56 | end # class Materialized 57 | end # class Relation 58 | end # module Generator 59 | end # module SQL 60 | end # module Axiom 61 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/relation/set.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | class Relation 7 | 8 | # Generates an SQL statement for a set relation 9 | class Set < Binary 10 | 11 | DIFFERENCE = 'EXCEPT'.freeze 12 | INTERSECTION = 'INTERSECT'.freeze 13 | UNION = 'UNION'.freeze 14 | 15 | # Normalize the headers of the operands 16 | # 17 | # This is necessary to make sure the columns are in the correct 18 | # order when generating SQL. 19 | # 20 | # @param [Relation::Operation::Set] relation 21 | # 22 | # @return [Relation::Operation::Set] 23 | # 24 | # @api private 25 | def self.normalize_operand_headers(relation) 26 | left = relation.left 27 | right = relation.right 28 | left_header = left.header 29 | if left_header.to_a != right.header.to_a 30 | relation.class.new(left, right.project(left_header)) 31 | else 32 | relation 33 | end 34 | end 35 | 36 | # Visit a Union 37 | # 38 | # @param [Algebra::Union] union 39 | # 40 | # @return [self] 41 | # 42 | # @api private 43 | def visit_axiom_algebra_union(union) 44 | set_operation(UNION) 45 | set_operands(union) 46 | set_name 47 | self 48 | end 49 | 50 | # Visit an Intersection 51 | # 52 | # @param [Algebra::Intersection] intersection 53 | # 54 | # @return [self] 55 | # 56 | # @api private 57 | def visit_axiom_algebra_intersection(intersection) 58 | set_operation(INTERSECTION) 59 | set_operands(intersection) 60 | set_name 61 | self 62 | end 63 | 64 | # Visit an Difference 65 | # 66 | # @param [Algebra::Difference] difference 67 | # 68 | # @return [self] 69 | # 70 | # @api private 71 | def visit_axiom_algebra_difference(difference) 72 | set_operation(DIFFERENCE) 73 | set_operands(difference) 74 | set_name 75 | self 76 | end 77 | 78 | private 79 | 80 | # Generate the SQL using the supplied method 81 | # 82 | # @return [#to_s] 83 | # 84 | # @api private 85 | def generate_sql(*) 86 | "(#{@left}) #{@operation} (#{@right})" 87 | end 88 | 89 | # Set the operands from the relation 90 | # 91 | # @param [Relation::Operation::Set] relation 92 | # 93 | # @return [undefined] 94 | # 95 | # @api private 96 | def set_operands(relation) 97 | super self.class.normalize_operand_headers(relation) 98 | end 99 | 100 | # Generates an SQL statement for base relation set operands 101 | class Base < Relation::Base; end 102 | 103 | end # class Set 104 | end # class Relation 105 | end # module Generator 106 | end # module SQL 107 | end # module Axiom 108 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/version.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | 7 | # Gem version 8 | VERSION = '0.2.0'.freeze 9 | 10 | end # module Generator 11 | end # module SQL 12 | end # module Axiom 13 | -------------------------------------------------------------------------------- /lib/axiom/sql/generator/visitor.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Axiom 4 | module SQL 5 | module Generator 6 | 7 | # Visit each node in a axiom AST and execute an associated method 8 | class Visitor 9 | 10 | # Raised when the object is not handled by the generator 11 | class UnknownObject < StandardError; end 12 | 13 | NAME_SEP_REGEXP = /([a-z])([A-Z])/.freeze 14 | NAME_REP = '\1_\2'.freeze 15 | DOUBLE_COLON = '::'.freeze 16 | UNDERSCORE = '_'.freeze 17 | 18 | # Lookup the handler method for a visitable class 19 | # 20 | # @param [Class] visitable_class 21 | # 22 | # @return [#to_sym] 23 | # 24 | # @raise [UnknownObject] 25 | # raised when the visitable object has no handler 26 | # 27 | # @api private 28 | def self.handler_for(visitable_class) 29 | handlers[visitable_class] or fail UnknownObject, "No handler for #{visitable_class} in #{self}" 30 | end 31 | 32 | # Return the handler cache that maps modules to method names 33 | # 34 | # @return [Hash] 35 | # 36 | # @api private 37 | def self.handlers 38 | @handlers ||= Hash.new do |hash, key| 39 | hash[key] = ancestor_methods_for(key).detect do |method| 40 | method_defined?(method) || private_method_defined?(method) 41 | end 42 | end 43 | end 44 | 45 | # Return handler methods for a module's ancestors 46 | # 47 | # @param [Module] mod 48 | # 49 | # @return [Array] 50 | # 51 | # @api private 52 | def self.ancestor_methods_for(mod) 53 | mod.ancestors.map { |ancestor| method_for(ancestor) } 54 | end 55 | 56 | # Return the handler method for a given module 57 | # 58 | # @param [Module] mod 59 | # 60 | # @return [Symbol] 61 | # 62 | # @api private 63 | def self.method_for(mod) 64 | name = "visit_#{mod.name}" 65 | name.gsub!(NAME_SEP_REGEXP, NAME_REP) 66 | name.gsub!(DOUBLE_COLON, UNDERSCORE) 67 | name.downcase! 68 | name.to_sym 69 | end 70 | 71 | private_class_method :handlers, :ancestor_methods_for, :method_for 72 | 73 | # Visit an object and generate SQL from each node 74 | # 75 | # @example 76 | # generator.visit(visitable) 77 | # 78 | # @param [Visitable] _visitable 79 | # 80 | # @return [self] 81 | # 82 | # @raise [UnknownObject] 83 | # raised when the visitable object has no handler 84 | # 85 | # @api public 86 | def visit(_visitable) 87 | fail NotImplementedError, "#{self.class}#visit must be implemented" 88 | end 89 | 90 | # Test if a visitable object has been visited 91 | # 92 | # @example 93 | # visitor.visited? # true or false 94 | # 95 | # @return [Boolean] 96 | # 97 | # @api public 98 | def visited? 99 | fail NotImplementedError, "#{self.class}#visited? must be implemented" 100 | end 101 | 102 | private 103 | 104 | # Dispatch the visitable object to a handler method 105 | # 106 | # @param [Visitable] visitable 107 | # 108 | # @return [#to_s] 109 | # 110 | # @raise [UnknownObject] 111 | # raised when the visitable object has no handler 112 | # 113 | # @api private 114 | def dispatch(visitable) 115 | send(self.class.handler_for(visitable.class), visitable) 116 | end 117 | 118 | end # class Visitor 119 | end # module Generator 120 | end # module SQL 121 | end # module Axiom 122 | -------------------------------------------------------------------------------- /spec/rcov.opts: -------------------------------------------------------------------------------- 1 | --exclude-only "spec/,^/" 2 | --sort coverage 3 | --callsites 4 | --xrefs 5 | --profile 6 | --text-summary 7 | --failure-threshold 100 8 | -------------------------------------------------------------------------------- /spec/shared/generated_sql_behavior.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | shared_examples_for 'a generated SQL expression' do 4 | it { should respond_to(:to_s) } 5 | 6 | its(:to_s) { should be_kind_of(String) } 7 | end 8 | 9 | shared_examples_for 'a generated SQL SELECT query' do 10 | it_should_behave_like 'a generated SQL expression' 11 | 12 | its(:name) { should == relation_name } 13 | 14 | its(:name) { should be_frozen } 15 | end 16 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | if ENV['COVERAGE'] == 'true' 4 | require 'simplecov' 5 | require 'coveralls' 6 | 7 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ 8 | SimpleCov::Formatter::HTMLFormatter, 9 | Coveralls::SimpleCov::Formatter 10 | ] 11 | 12 | SimpleCov.start do 13 | command_name 'spec:unit' 14 | 15 | add_filter 'config' 16 | add_filter 'spec' 17 | add_filter 'vendor' 18 | 19 | minimum_coverage 100 20 | end 21 | end 22 | 23 | require 'devtools/spec_helper' 24 | require 'axiom-sql-generator' 25 | 26 | include Axiom 27 | 28 | Types.finalize 29 | 30 | RSpec.configure do |config| 31 | config.expect_with :rspec do |expect_with| 32 | expect_with.syntax = :expect 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/support/config_alias.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rbconfig' 4 | 5 | ::Config = RbConfig unless defined?(::Config) 6 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/attribute/visit_axiom_attribute_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Attribute, '#visit_axiom_attribute' do 6 | subject { object.visit_axiom_attribute(attribute) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Attribute } } 9 | let(:attribute) { Attribute::Integer.new(:id) } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('"id"') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/class_methods/parenthesize_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator, '.parenthesize!' do 6 | subject { object.parenthesize!(string) } 7 | 8 | let(:string) { '1 + 1' } 9 | let(:object) { self.class.described_class } 10 | 11 | it_should_behave_like 'a generated SQL expression' 12 | 13 | it 'modifies the string inline' do 14 | should equal(string) 15 | end 16 | 17 | its(:to_s) { should == '(1 + 1)' } 18 | end 19 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/direction/visit_axiom_relation_operation_sorted_ascending_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Direction, '#visit_axiom_relation_operation_sorted_ascending' do 6 | subject { object.visit_axiom_relation_operation_sorted_ascending(direction) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Direction } } 9 | let(:direction) { Attribute::Integer.new(:id).asc } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('"id"') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/direction/visit_axiom_relation_operation_sorted_descending_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Direction, '#visit_axiom_relation_operation_sorted_descending' do 6 | subject { object.visit_axiom_relation_operation_sorted_descending(direction) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Direction } } 9 | let(:direction) { Attribute::Integer.new(:id).desc } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('"id" DESC') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_count_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Aggregate, '#visit_axiom_aggregate_count' do 6 | subject { object.visit_axiom_aggregate_count(count) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Aggregate } } 9 | let(:attribute) { Attribute::Integer.new(:id) } 10 | let(:count) { attribute.count } 11 | let(:object) { described_class.new } 12 | 13 | it_should_behave_like 'a generated SQL expression' 14 | 15 | its(:to_s) { should eql('COUNT ("id")') } 16 | end 17 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_maximum_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Aggregate, '#visit_axiom_aggregate_maximum' do 6 | subject { object.visit_axiom_aggregate_maximum(maximum) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Aggregate } } 9 | let(:attribute) { Attribute::Integer.new(:id) } 10 | let(:maximum) { attribute.maximum } 11 | let(:object) { described_class.new } 12 | 13 | it_should_behave_like 'a generated SQL expression' 14 | 15 | its(:to_s) { should eql('MAX ("id")') } 16 | end 17 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_mean_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Aggregate, '#visit_axiom_aggregate_mean' do 6 | subject { object.visit_axiom_aggregate_mean(mean) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Aggregate } } 9 | let(:attribute) { Attribute::Integer.new(:id) } 10 | let(:mean) { attribute.mean } 11 | let(:object) { described_class.new } 12 | 13 | it_should_behave_like 'a generated SQL expression' 14 | 15 | its(:to_s) { should eql('AVG ("id")') } 16 | end 17 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_minimum_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Aggregate, '#visit_axiom_aggregate_minimum' do 6 | subject { object.visit_axiom_aggregate_minimum(minimum) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Aggregate } } 9 | let(:attribute) { Attribute::Integer.new(:id) } 10 | let(:minimum) { attribute.minimum } 11 | let(:object) { described_class.new } 12 | 13 | it_should_behave_like 'a generated SQL expression' 14 | 15 | its(:to_s) { should eql('MIN ("id")') } 16 | end 17 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_standard_deviation_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Aggregate, '#visit_axiom_aggregate_standard_deviation' do 6 | subject { object.visit_axiom_aggregate_standard_deviation(standard_deviation) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Aggregate } } 9 | let(:attribute) { Attribute::Integer.new(:id) } 10 | let(:standard_deviation) { attribute.standard_deviation } 11 | let(:object) { described_class.new } 12 | 13 | it_should_behave_like 'a generated SQL expression' 14 | 15 | its(:to_s) { should eql('STDDEV_POP ("id")') } 16 | end 17 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_sum_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Aggregate, '#visit_axiom_aggregate_sum' do 6 | subject { object.visit_axiom_aggregate_sum(sum) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Aggregate } } 9 | let(:attribute) { Attribute::Integer.new(:id) } 10 | let(:sum) { attribute.sum } 11 | let(:object) { described_class.new } 12 | 13 | it_should_behave_like 'a generated SQL expression' 14 | 15 | its(:to_s) { should eql('COALESCE (SUM ("id"), 0)') } 16 | end 17 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/aggregate/visit_axiom_aggregate_variance_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Aggregate, '#visit_axiom_aggregate_variance' do 6 | subject { object.visit_axiom_aggregate_variance(variance) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Aggregate } } 9 | let(:attribute) { Attribute::Integer.new(:id) } 10 | let(:variance) { attribute.variance } 11 | let(:object) { described_class.new } 12 | 13 | it_should_behave_like 'a generated SQL expression' 14 | 15 | its(:to_s) { should eql('VAR_POP ("id")') } 16 | end 17 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/connective/visit_axiom_function_connective_conjunction_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Connective, '#visit_axiom_function_connective_conjunction' do 6 | subject { object.visit_axiom_function_connective_conjunction(conjunction) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Connective } } 9 | let(:attribute) { Attribute::Integer.new(:id) } 10 | let(:conjunction) { attribute.eq(1).and(attribute.ne(2)) } 11 | let(:object) { described_class.new } 12 | 13 | before do 14 | described_class.class_eval { include SQL::Generator::Function::Predicate } 15 | end 16 | 17 | it_should_behave_like 'a generated SQL expression' 18 | 19 | its(:to_s) { should eql('("id" = 1 AND "id" <> 2)') } 20 | end 21 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/connective/visit_axiom_function_connective_disjunction_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Connective, '#visit_axiom_function_connective_disjunction' do 6 | subject { object.visit_axiom_function_connective_disjunction(disjunction) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Connective } } 9 | let(:attribute) { Attribute::Integer.new(:id) } 10 | let(:disjunction) { attribute.eq(1).or(attribute.eq(2)) } 11 | let(:object) { described_class.new } 12 | 13 | before do 14 | described_class.class_eval { include SQL::Generator::Function::Predicate } 15 | end 16 | 17 | it_should_behave_like 'a generated SQL expression' 18 | 19 | its(:to_s) { should eql('("id" = 1 OR "id" = 2)') } 20 | end 21 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/connective/visit_axiom_function_connective_negation_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Connective, '#visit_axiom_function_connective_negation' do 6 | subject { object.visit_axiom_function_connective_negation(negation) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Connective } } 9 | let(:attribute) { Attribute::Integer.new(:id) } 10 | let(:negation) { Function::Connective::Negation.new(attribute.eq(1)) } 11 | let(:object) { described_class.new } 12 | 13 | before do 14 | described_class.class_eval { include SQL::Generator::Function::Predicate } 15 | end 16 | 17 | it_should_behave_like 'a generated SQL expression' 18 | 19 | its(:to_s) { should eql('NOT ("id" = 1)') } 20 | end 21 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_absolute_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Numeric, '#visit_axiom_function_numeric_absolute' do 6 | subject { object.visit_axiom_function_numeric_absolute(absolute) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Numeric } } 9 | let(:absolute) { Attribute::Integer.new(:number).abs } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('ABS ("number")') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_addition_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Numeric, '#visit_axiom_function_numeric_addition' do 6 | subject { object.visit_axiom_function_numeric_addition(addition) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Numeric } } 9 | let(:addition) { Attribute::Integer.new(:number).add(1) } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('("number" + 1)') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_division_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Numeric, '#visit_axiom_function_numeric_division' do 6 | subject { object.visit_axiom_function_numeric_division(division) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Numeric } } 9 | let(:division) { Attribute::Integer.new(:number).divide(1) } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('("number" / 1)') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_exponentiation_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Numeric, '#visit_axiom_function_numeric_exponentiation' do 6 | subject { object.visit_axiom_function_numeric_exponentiation(exponentiation) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Numeric } } 9 | let(:exponentiation) { Attribute::Integer.new(:number).exponent(1) } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('POWER ("number", 1)') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_modulo_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Numeric, '#visit_axiom_function_numeric_modulo' do 6 | subject { object.visit_axiom_function_numeric_modulo(modulo) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Numeric } } 9 | let(:modulo) { Attribute::Integer.new(:number).modulo(1) } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('MOD ("number", 1)') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_multiplication_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Numeric, '#visit_axiom_function_numeric_multiplication' do 6 | subject { object.visit_axiom_function_numeric_multiplication(multiplication) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Numeric } } 9 | let(:multiplication) { Attribute::Integer.new(:number).multiply(1) } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('("number" * 1)') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_square_root_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Numeric, '#visit_axiom_function_numeric_square_root' do 6 | subject { object.visit_axiom_function_numeric_square_root(square_root) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Numeric } } 9 | let(:square_root) { Attribute::Integer.new(:number).square_root } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('SQRT ("number")') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_subtraction_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Numeric, '#visit_axiom_function_numeric_subtraction' do 6 | subject { object.visit_axiom_function_numeric_subtraction(subtraction) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Numeric } } 9 | let(:subtraction) { Attribute::Integer.new(:number).subtract(1) } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('("number" - 1)') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_unary_minus_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Numeric, '#visit_axiom_function_numeric_unary_minus' do 6 | subject { object.visit_axiom_function_numeric_unary_minus(unary_minus) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Numeric } } 9 | let(:unary_minus) { Attribute::Integer.new(:number).unary_minus } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('- ("number")') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/numeric/visit_axiom_function_numeric_unary_plus_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Numeric, '#visit_axiom_function_numeric_unary_plus' do 6 | subject { object.visit_axiom_function_numeric_unary_plus(unary_plus) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Numeric } } 9 | let(:unary_plus) { Attribute::Integer.new(:number).unary_plus } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('+ ("number")') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_equality_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Predicate, '#visit_axiom_function_predicate_equality' do 6 | subject { object.visit_axiom_function_predicate_equality(equality) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Predicate } } 9 | let(:attribute) { Attribute::Integer.new(:id) } 10 | let(:object) { described_class.new } 11 | 12 | context 'when the right operand is not nil' do 13 | let(:equality) { attribute.eq(1) } 14 | 15 | it_should_behave_like 'a generated SQL expression' 16 | 17 | its(:to_s) { should eql('"id" = 1') } 18 | end 19 | 20 | context 'when the right operand is nil' do 21 | let(:equality) { attribute.eq(nil) } 22 | 23 | it_should_behave_like 'a generated SQL expression' 24 | 25 | its(:to_s) { should eql('"id" IS NULL') } 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_exclusion_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Predicate, '#visit_axiom_function_predicate_exclusion' do 6 | subject { object.visit_axiom_function_predicate_exclusion(exclusion) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Predicate } } 9 | let(:attribute) { Attribute::Integer.new(:id) } 10 | let(:object) { described_class.new } 11 | 12 | before do 13 | described_class.class_eval { include SQL::Generator::Function::Connective } 14 | end 15 | 16 | context 'when right operand is an inclusive Range' do 17 | let(:exclusion) { attribute.exclude(1..10) } 18 | 19 | it_should_behave_like 'a generated SQL expression' 20 | 21 | its(:to_s) { should eql('"id" NOT BETWEEN 1 AND 10') } 22 | end 23 | 24 | context 'when right operand is an exclusive Range' do 25 | let(:exclusion) { attribute.exclude(1...10) } 26 | 27 | it_should_behave_like 'a generated SQL expression' 28 | 29 | its(:to_s) { should eql('("id" < 1 OR "id" >= 10)') } 30 | end 31 | 32 | context 'when right operand is an Array' do 33 | let(:exclusion) { attribute.exclude([1, 2]) } 34 | 35 | it_should_behave_like 'a generated SQL expression' 36 | 37 | its(:to_s) { should eql('"id" NOT IN (1, 2)') } 38 | end 39 | 40 | context 'when right operand is an empty Array' do 41 | let(:exclusion) { attribute.exclude([]) } 42 | 43 | it_should_behave_like 'a generated SQL expression' 44 | 45 | its(:to_s) { should eql('TRUE') } 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_greater_than_or_equal_to_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Predicate, '#visit_axiom_function_predicate_greater_than_or_equal_to' do 6 | subject { object.visit_axiom_function_predicate_greater_than_or_equal_to(greater_than_or_equal_to) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Predicate } } 9 | let(:greater_than_or_equal_to) { Attribute::Integer.new(:id).gte(1) } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('"id" >= 1') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_greater_than_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Predicate, '#visit_axiom_function_predicate_greater_than' do 6 | subject { object.visit_axiom_function_predicate_greater_than(greater_than) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Predicate } } 9 | let(:greater_than) { Attribute::Integer.new(:id).gt(1) } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('"id" > 1') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_inclusion_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Predicate, '#visit_axiom_function_predicate_inclusion' do 6 | subject { object.visit_axiom_function_predicate_inclusion(inclusion) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Predicate } } 9 | let(:attribute) { Attribute::Integer.new(:id) } 10 | let(:object) { described_class.new } 11 | 12 | before do 13 | described_class.class_eval { include SQL::Generator::Function::Connective } 14 | end 15 | 16 | context 'when right operand is an inclusive Range' do 17 | let(:inclusion) { attribute.include(1..10) } 18 | 19 | it_should_behave_like 'a generated SQL expression' 20 | 21 | its(:to_s) { should eql('"id" BETWEEN 1 AND 10') } 22 | end 23 | 24 | context 'when right operand is an exclusive Range' do 25 | let(:inclusion) { attribute.include(1...10) } 26 | 27 | it_should_behave_like 'a generated SQL expression' 28 | 29 | its(:to_s) { should eql('("id" >= 1 AND "id" < 10)') } 30 | end 31 | 32 | context 'when right operand is an Array' do 33 | let(:inclusion) { attribute.include([1, 2]) } 34 | 35 | it_should_behave_like 'a generated SQL expression' 36 | 37 | its(:to_s) { should eql('"id" IN (1, 2)') } 38 | end 39 | 40 | context 'when right operand is an empty Array' do 41 | let(:inclusion) { attribute.include([]) } 42 | 43 | it_should_behave_like 'a generated SQL expression' 44 | 45 | its(:to_s) { should eql('FALSE') } 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_inequality_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Predicate, '#visit_axiom_function_predicate_inequality' do 6 | subject { object.visit_axiom_function_predicate_inequality(inequality) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Predicate } } 9 | let(:object) { described_class.new } 10 | 11 | context 'and the left attribute is optional' do 12 | let(:attribute) { Attribute::Integer.new(:age, required: false) } 13 | let(:inequality) { attribute.ne(1) } 14 | 15 | it_should_behave_like 'a generated SQL expression' 16 | 17 | its(:to_s) { should eql('("age" <> 1 OR "age" IS NULL)') } 18 | end 19 | 20 | context 'and the right attribute is optional' do 21 | let(:attribute) { Attribute::Integer.new(:age, required: false) } 22 | let(:inequality) { Function::Predicate::Inequality.new(1, attribute) } 23 | 24 | it_should_behave_like 'a generated SQL expression' 25 | 26 | its(:to_s) { should eql('(1 <> "age" OR "age" IS NULL)') } 27 | end 28 | 29 | context 'and the left is a value' do 30 | let(:attribute) { Attribute::Integer.new(:id) } 31 | let(:inequality) { Function::Predicate::Inequality.new(1, attribute) } 32 | 33 | it_should_behave_like 'a generated SQL expression' 34 | 35 | its(:to_s) { should eql('1 <> "id"') } 36 | end 37 | 38 | context 'and the right is a value' do 39 | let(:attribute) { Attribute::Integer.new(:id) } 40 | let(:inequality) { attribute.ne(1) } 41 | 42 | it_should_behave_like 'a generated SQL expression' 43 | 44 | its(:to_s) { should eql('"id" <> 1') } 45 | end 46 | 47 | context 'and the right is a nil value' do 48 | let(:attribute) { Attribute::Integer.new(:id) } 49 | let(:inequality) { attribute.ne(nil) } 50 | 51 | it_should_behave_like 'a generated SQL expression' 52 | 53 | its(:to_s) { should eql('"id" IS NOT NULL') } 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_less_than_or_equal_to_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Predicate, '#visit_axiom_function_predicate_less_than_or_equal_to' do 6 | subject { object.visit_axiom_function_predicate_less_than_or_equal_to(less_than_or_equal_to) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Predicate } } 9 | let(:less_than_or_equal_to) { Attribute::Integer.new(:id).lte(1) } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('"id" <= 1') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/predicate/visit_axiom_function_predicate_less_than_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Predicate, '#visit_axiom_function_predicate_less_than' do 6 | subject { object.visit_axiom_function_predicate_less_than(less_than) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Predicate } } 9 | let(:less_than) { Attribute::Integer.new(:id).lt(1) } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('"id" < 1') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/proposition/visit_axiom_function_proposition_contradiction_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Proposition, '#visit_axiom_function_proposition_contradiction' do 6 | subject { object.visit_axiom_function_proposition_contradiction(contradiction) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Proposition } } 9 | let(:contradiction) { Function::Proposition::Contradiction.instance } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('FALSE') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/proposition/visit_axiom_function_proposition_tautology_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::Proposition, '#visit_axiom_function_proposition_tautology' do 6 | subject { object.visit_axiom_function_proposition_tautology(tautology) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::Proposition } } 9 | let(:tautology) { Function::Proposition::Tautology.instance } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('TRUE') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/function/string/visit_axiom_function_string_length_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Function::String, '#visit_axiom_function_string_length' do 6 | subject { object.visit_axiom_function_string_length(length) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Function::String } } 9 | let(:length) { Attribute::String.new(:name).length } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('LENGTH ("name")') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/identifier/visit_identifier_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Identifier, '#visit_identifier' do 6 | subject { object.visit_identifier(identifier) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Identifier } } 9 | let(:object) { described_class.new } 10 | 11 | context 'with an identifier containing no quotes' do 12 | let(:identifier) { 'users' } 13 | 14 | it_should_behave_like 'a generated SQL expression' 15 | 16 | its(:to_s) { should eql('"users"') } 17 | end 18 | 19 | context 'with an identifier containing quotes' do 20 | let(:identifier) { 'users"name' } 21 | 22 | it_should_behave_like 'a generated SQL expression' 23 | 24 | its(:to_s) { should eql('"users""name"') } 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/literal/class_methods/dup_frozen_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Literal, '.dup_frozen' do 6 | subject { object.dup_frozen(object_arg) } 7 | 8 | let(:object) { SQL::Generator::Literal } 9 | 10 | context 'with a frozen object' do 11 | let(:object_arg) { Time.now.freeze } 12 | 13 | it { should_not equal(object_arg) } 14 | 15 | it { should == object_arg } 16 | end 17 | 18 | context 'with a non-frozen object' do 19 | let(:object_arg) { Time.now } 20 | 21 | it { should equal(object_arg) } 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/literal/visit_class_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Literal, '#visit_class' do 6 | subject { object.visit_class(klass) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Literal } } 9 | let(:object) { described_class.new } 10 | 11 | before do 12 | Object.class_eval { remove_const(:NamedClass) if const_defined?(:NamedClass) } 13 | class ::NamedClass; end 14 | end 15 | 16 | context 'with a named class' do 17 | let(:klass) { NamedClass.freeze } 18 | 19 | it_should_behave_like 'a generated SQL expression' 20 | 21 | its(:to_s) { should eql("'NamedClass'") } 22 | end 23 | 24 | context 'with an anonymous class' do 25 | let(:klass) { described_class } 26 | 27 | it_should_behave_like 'a generated SQL expression' 28 | 29 | its(:to_s) { should eql('NULL') } 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/literal/visit_date_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Literal, '#visit_date' do 6 | subject { object.visit_date(date) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Literal } } 9 | let(:date) { Date.new(2010, 12, 31).freeze } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql("'2010-12-31'") } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/literal/visit_date_time_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Literal, '#visit_date_time' do 6 | subject { object.visit_date_time(date_time) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Literal } } 9 | let(:nsec_in_seconds) { Rational(nsec, 10**9) } 10 | let(:object) { described_class.new } 11 | 12 | context 'when the DateTime is UTC' do 13 | let(:offset) { 0 } 14 | 15 | context 'and the nanoseconds are equal to 0' do 16 | let(:nsec) { 0 } 17 | let(:date_time) { DateTime.new(2010, 12, 31, 23, 59, 59 + nsec_in_seconds, offset).freeze } 18 | 19 | it_should_behave_like 'a generated SQL expression' 20 | 21 | its(:to_s) { should eql("'2010-12-31T23:59:59.000000000+00:00'") } 22 | end 23 | 24 | context 'and the nanoseconds are greater than 0' do 25 | let(:nsec) { 1 } 26 | let(:date_time) { DateTime.new(2010, 12, 31, 23, 59, 59 + nsec_in_seconds, offset).freeze } 27 | 28 | it_should_behave_like 'a generated SQL expression' 29 | 30 | its(:to_s) { should eql("'2010-12-31T23:59:59.000000001+00:00'") } 31 | end 32 | end 33 | 34 | context 'when the DateTime is not UTC' do 35 | let(:offset) { Rational(-8, 24) } 36 | 37 | context 'and the nanoseconds are equal to 0' do 38 | let(:nsec) { 0 } 39 | let(:date_time) { DateTime.new(2010, 12, 31, 15, 59, 59 + nsec_in_seconds, offset).freeze } 40 | 41 | it_should_behave_like 'a generated SQL expression' 42 | 43 | its(:to_s) { should eql("'2010-12-31T23:59:59.000000000+00:00'") } 44 | end 45 | 46 | context 'and the nanoseconds are greater than 0' do 47 | let(:nsec) { 1 } 48 | let(:date_time) { DateTime.new(2010, 12, 31, 15, 59, 59 + nsec_in_seconds, offset).freeze } 49 | 50 | it_should_behave_like 'a generated SQL expression' 51 | 52 | its(:to_s) { should eql("'2010-12-31T23:59:59.000000001+00:00'") } 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/literal/visit_enumerable_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Literal, '#visit_enumerable' do 6 | subject { object.visit_enumerable(enumerable) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Literal } } 9 | let(:enumerable) { [1, 2].freeze } 10 | let(:object) { described_class.new } 11 | 12 | it_should_behave_like 'a generated SQL expression' 13 | 14 | its(:to_s) { should eql('(1, 2)') } 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/literal/visit_false_class_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Literal, '#visit_false_class' do 6 | subject { object.visit_false_class(false) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Literal } } 9 | let(:object) { described_class.new } 10 | 11 | it_should_behave_like 'a generated SQL expression' 12 | 13 | its(:to_s) { should eql('FALSE') } 14 | end 15 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/literal/visit_nil_class_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Literal, '#visit_nil_class' do 6 | subject { object.visit_nil_class(nil) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Literal } } 9 | let(:object) { described_class.new } 10 | 11 | it_should_behave_like 'a generated SQL expression' 12 | 13 | its(:to_s) { should eql('NULL') } 14 | end 15 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/literal/visit_numeric_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Literal, '#visit_numeric' do 6 | subject { object.visit_numeric(numeric) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Literal } } 9 | let(:object) { described_class.new } 10 | 11 | context 'with an Integer' do 12 | let(:numeric) { 1 } 13 | 14 | it_should_behave_like 'a generated SQL expression' 15 | 16 | its(:to_s) { should eql('1') } 17 | end 18 | 19 | context 'with a Float' do 20 | let(:numeric) { 1.0 } 21 | 22 | it_should_behave_like 'a generated SQL expression' 23 | 24 | its(:to_s) { should eql('1.0') } 25 | end 26 | 27 | context 'with a BigDecimal' do 28 | let(:numeric) { BigDecimal('1.0') } 29 | 30 | it_should_behave_like 'a generated SQL expression' 31 | 32 | its(:to_s) { should eql('0.1E1') } 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/literal/visit_string_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Literal, '#visit_string' do 6 | subject { object.visit_string(string) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Literal } } 9 | let(:object) { described_class.new } 10 | 11 | context 'with a string containing no quotes' do 12 | let(:string) { 'string'.freeze } 13 | 14 | it_should_behave_like 'a generated SQL expression' 15 | 16 | its(:to_s) { should eql("'string'") } 17 | end 18 | 19 | context 'with a string containing quotes' do 20 | let(:string) { "string'name".freeze } 21 | 22 | it_should_behave_like 'a generated SQL expression' 23 | 24 | its(:to_s) { should eql("'string''name'") } 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/literal/visit_time_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Literal, '#visit_time' do 6 | subject { object.visit_time(time) } 7 | 8 | # Time#iso8601 is currently broken in JRuby 1.7.0.dev when fractional seconds are not 0 9 | def self.time_iso8601_broken? 10 | RUBY_PLATFORM.include?('java') && JRUBY_VERSION <= '1.7.0.dev' && RUBY_VERSION >= '1.9.2' 11 | end 12 | 13 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Literal } } 14 | let(:object) { described_class.new } 15 | 16 | before :all do 17 | @original_tz = ENV['TZ'] 18 | end 19 | 20 | after :all do 21 | ENV['TZ'] = @original_tz 22 | end 23 | 24 | context 'when the Time is UTC' do 25 | context 'and the microseconds are equal to 0' do 26 | let(:usec) { 0 } 27 | let(:time) { Time.utc(2010, 12, 31, 23, 59, 59, usec).freeze } 28 | 29 | it_should_behave_like 'a generated SQL expression' 30 | 31 | its(:to_s) { should eql("'2010-12-31T23:59:59.000000000Z'") } 32 | end 33 | 34 | context 'and the microseconds are greater than 0' do 35 | let(:usec) { 1 } 36 | let(:time) { Time.utc(2010, 12, 31, 23, 59, 59, usec).freeze } 37 | 38 | it_should_behave_like 'a generated SQL expression' 39 | 40 | unless time_iso8601_broken? 41 | its(:to_s) { should eql("'2010-12-31T23:59:59.000001000Z'") } 42 | end 43 | end 44 | end 45 | 46 | context 'when the Time is local, and the local time zone is UTC' do 47 | before :all do 48 | ENV['TZ'] = 'UTC' 49 | end 50 | 51 | context 'and the microseconds are equal to 0' do 52 | let(:usec) { 0 } 53 | let(:time) { Time.local(2010, 12, 31, 23, 59, 59, usec).freeze } 54 | 55 | it_should_behave_like 'a generated SQL expression' 56 | 57 | its(:to_s) { should eql("'2010-12-31T23:59:59.000000000Z'") } 58 | end 59 | 60 | context 'and the microseconds are greater than 0' do 61 | let(:usec) { 1 } 62 | let(:time) { Time.local(2010, 12, 31, 23, 59, 59, usec).freeze } 63 | 64 | it_should_behave_like 'a generated SQL expression' 65 | 66 | unless time_iso8601_broken? 67 | its(:to_s) { should eql("'2010-12-31T23:59:59.000001000Z'") } 68 | end 69 | end 70 | end 71 | 72 | context 'when the Time is local, and the local time zone is not UTC' do 73 | before :all do 74 | ENV['TZ'] = 'America/Vancouver' 75 | end 76 | 77 | context 'and the microseconds are equal to 0' do 78 | let(:usec) { 0 } 79 | let(:time) { Time.local(2010, 12, 31, 15, 59, 59, usec).freeze } 80 | 81 | it_should_behave_like 'a generated SQL expression' 82 | 83 | its(:to_s) { should eql("'2010-12-31T23:59:59.000000000Z'") } 84 | end 85 | 86 | context 'and the microseconds are greater than 0' do 87 | let(:usec) { 1 } 88 | let(:time) { Time.local(2010, 12, 31, 15, 59, 59, usec).freeze } 89 | 90 | it_should_behave_like 'a generated SQL expression' 91 | 92 | unless time_iso8601_broken? 93 | its(:to_s) { should eql("'2010-12-31T23:59:59.000001000Z'") } 94 | end 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/literal/visit_true_class_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Literal, '#visit_true_class' do 6 | subject { object.visit_true_class(true) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Visitor) { include SQL::Generator::Literal } } 9 | let(:object) { described_class.new } 10 | 11 | it_should_behave_like 'a generated SQL expression' 12 | 13 | its(:to_s) { should eql('TRUE') } 14 | end 15 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/binary/base/to_subquery_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation::Binary::Base, '#to_subquery' do 6 | subject { object.to_subquery } 7 | 8 | let(:id) { Attribute::Integer.new(:id) } 9 | let(:name) { Attribute::String.new(:name) } 10 | let(:age) { Attribute::Integer.new(:age, required: false) } 11 | let(:header) { [id, name, age] } 12 | let(:body) { [[1, 'Dan Kubb', 35]].each } 13 | let(:base_relation) { Relation::Base.new('users', header, body) } 14 | let(:object) { described_class.new } 15 | 16 | context 'when no object visited' do 17 | it_should_behave_like 'an idempotent method' 18 | 19 | it { should respond_to(:to_s) } 20 | 21 | it { should be_frozen } 22 | 23 | its(:to_s) { should == '' } 24 | end 25 | 26 | context 'when a base relation is visited' do 27 | before do 28 | object.visit(base_relation) 29 | end 30 | 31 | it_should_behave_like 'a generated SQL expression' 32 | 33 | its(:to_s) { should eql('"users"') } 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/binary/base/visit_axiom_relation_base_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation::Binary::Base, '#visit_axiom_relation_base' do 6 | subject { object.visit_axiom_relation_base(base_relation) } 7 | 8 | let(:relation_name) { 'users' } 9 | let(:id) { Attribute::Integer.new(:id) } 10 | let(:name) { Attribute::String.new(:name) } 11 | let(:age) { Attribute::Integer.new(:age, required: false) } 12 | let(:header) { [id, name, age] } 13 | let(:body) { [[1, 'Dan Kubb', 35]].each } 14 | let(:base_relation) { Relation::Base.new(relation_name, header, body) } 15 | let(:object) { described_class.new } 16 | 17 | it_should_behave_like 'a generated SQL SELECT query' 18 | 19 | its(:name) { should eql('users') } 20 | 21 | its(:to_subquery) { should eql('"users"') } 22 | end 23 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/binary/to_s_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation::Binary, '#to_s' do 6 | subject { object.to_s } 7 | 8 | let(:id) { Attribute::Integer.new(:id) } 9 | let(:name) { Attribute::String.new(:name) } 10 | let(:age) { Attribute::Integer.new(:age, required: false) } 11 | let(:header) { [id, name, age] } 12 | let(:body) { [[1, 'Dan Kubb', 35]].each } 13 | let(:base_relation) { Relation::Base.new('users', header, body) } 14 | let(:object) { described_class.new } 15 | 16 | context 'when no object visited' do 17 | it_should_behave_like 'an idempotent method' 18 | 19 | it { should respond_to(:to_s) } 20 | 21 | it { should be_frozen } 22 | 23 | its(:to_s) { should == '' } 24 | end 25 | 26 | context 'when a join is visited' do 27 | before do 28 | object.visit(base_relation.join(base_relation)) 29 | end 30 | 31 | it_should_behave_like 'a generated SQL expression' 32 | 33 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" AS "left" NATURAL JOIN "users" AS "right"') } 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/binary/to_subquery_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation::Binary, '#to_subquery' do 6 | subject { object.to_subquery } 7 | 8 | let(:id) { Attribute::Integer.new(:id) } 9 | let(:name) { Attribute::String.new(:name) } 10 | let(:age) { Attribute::Integer.new(:age, required: false) } 11 | let(:header) { [id, name, age] } 12 | let(:body) { [[1, 'Dan Kubb', 35]].each } 13 | let(:base_relation) { Relation::Base.new('users', header, body) } 14 | let(:object) { described_class.new } 15 | 16 | context 'when no object visited' do 17 | it_should_behave_like 'an idempotent method' 18 | 19 | it { should respond_to(:to_s) } 20 | 21 | it { should be_frozen } 22 | 23 | its(:to_s) { should == '' } 24 | end 25 | 26 | context 'when a join is visited' do 27 | before do 28 | object.visit(base_relation.join(base_relation)) 29 | end 30 | 31 | it_should_behave_like 'a generated SQL expression' 32 | 33 | its(:to_s) { should eql('(SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right")') } 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/class_methods/visit_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation, '.visit' do 6 | subject { object.visit(relation) } 7 | 8 | let(:id) { Attribute::Integer.new(:id) } 9 | let(:name) { Attribute::String.new(:name) } 10 | let(:age) { Attribute::Integer.new(:age, required: false) } 11 | let(:header) { [id, name, age] } 12 | let(:body) { [[1, 'Dan Kubb', 35]].each } 13 | let(:base_relation) { Relation::Base.new('users', header, body) } 14 | let(:object) { described_class } 15 | 16 | context 'when the relation is an insertion operation' do 17 | let(:relation) { base_relation.insert(base_relation) } 18 | 19 | it { should be_kind_of(SQL::Generator::Relation::Insertion) } 20 | 21 | its(:name) { should == 'users' } 22 | 23 | it { should be_frozen } 24 | end 25 | 26 | context 'when the relation is a set operation' do 27 | let(:relation) { base_relation.union(base_relation) } 28 | 29 | it { should be_kind_of(SQL::Generator::Relation::Set) } 30 | 31 | its(:name) { should == 'users' } 32 | 33 | it { should be_frozen } 34 | end 35 | 36 | context 'when the relation is a binary operation' do 37 | let(:relation) { base_relation.join(base_relation.project([:id])) } 38 | 39 | it { should be_kind_of(SQL::Generator::Relation::Binary) } 40 | 41 | its(:name) { should == 'users' } 42 | 43 | it { should be_frozen } 44 | end 45 | 46 | context 'when the relation is a unary operation' do 47 | let(:relation) { base_relation.project([:id]) } 48 | 49 | it { should be_kind_of(SQL::Generator::Relation::Unary) } 50 | 51 | its(:name) { should == 'users' } 52 | 53 | it { should be_frozen } 54 | end 55 | 56 | context 'when the relation is a base relation' do 57 | let(:relation) { base_relation } 58 | 59 | it { should be_kind_of(SQL::Generator::Relation::Base) } 60 | 61 | its(:name) { should == 'users' } 62 | 63 | it { should be_frozen } 64 | end 65 | 66 | context 'when the relation is invalid' do 67 | let(:relation) { double('Invalid Relation') } 68 | 69 | specify { expect { subject }.to raise_error(SQL::Generator::InvalidRelationError, "#{relation.class} is not a visitable relation") } 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/insertion/to_subquery_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation::Insertion, '#to_subquery' do 6 | subject { object.to_subquery } 7 | 8 | let(:object) { described_class.new } 9 | 10 | it 'delegates to #to_s' do 11 | sql = double('sql') 12 | expect(object).to receive(:to_s).with(no_args).and_return(sql) 13 | should equal(sql) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/materialized/visit_axiom_relation_materialized_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation::Materialized, '#visit_axiom_relation_materialized' do 6 | subject { object.visit_axiom_relation_materialized(relation) } 7 | 8 | let(:object) { described_class.new } 9 | let(:header) { [[:id, Integer], [:name, String]] } 10 | 11 | context 'with an non-empty relation' do 12 | let(:relation) { Relation.new(header, [[1, 'John Doe'], [2, 'Jane Doe']]) } 13 | 14 | it_should_behave_like 'a generated SQL expression' 15 | 16 | its(:to_s) { should eql('VALUES (1, \'John Doe\'), (2, \'Jane Doe\')') } 17 | its(:to_subquery) { should eql('(VALUES (1, \'John Doe\'), (2, \'Jane Doe\'))') } 18 | end 19 | 20 | context 'with an empty relation' do 21 | let(:relation) { Relation.new(header, []) } 22 | 23 | it_should_behave_like 'a generated SQL expression' 24 | 25 | its(:to_s) { should eql('SELECT 0 LIMIT 0') } 26 | its(:to_subquery) { should eql('(SELECT 0 LIMIT 0)') } 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/materialized/visited_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation::Materialized, '#visited?' do 6 | subject { object.visited? } 7 | 8 | let(:object) { described_class.new } 9 | let(:relation) { Relation.new([[:id, Integer]], [[]]) } 10 | 11 | context 'when the relation is visited' do 12 | before do 13 | object.visit_axiom_relation_materialized(relation) 14 | end 15 | 16 | it_should_behave_like 'an idempotent method' 17 | 18 | it { should be(true) } 19 | end 20 | 21 | context 'when the relation is not visited' do 22 | it_should_behave_like 'an idempotent method' 23 | 24 | it { should be(false) } 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/name_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation, '#name' do 6 | subject { object.name } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Relation) } 9 | let(:object) { described_class.new } 10 | 11 | context 'when name is nil' do 12 | it_should_behave_like 'an idempotent method' 13 | 14 | it { should be_nil } 15 | end 16 | 17 | context 'when name is set' do 18 | let(:name) { 'test' } 19 | 20 | before do 21 | # subclasses set @name, but nothing in this class 22 | # does does so simulate it being set 23 | object.instance_variable_set(:@name, name) 24 | end 25 | 26 | it_should_behave_like 'an idempotent method' 27 | 28 | it { should equal(name) } 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/set/class_methods/normalize_operand_headers_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation::Set, '.normalize_operand_headers' do 6 | subject { object.normalize_operand_headers(relation) } 7 | 8 | let(:object) { described_class } 9 | let(:relation) { left.union(right) } 10 | let(:relation_name) { 'test' } 11 | let(:header) { [[:id, Integer], [:name, String]] } 12 | let(:body) { [].each } 13 | 14 | context 'when the left and right headers are sorted in the same order' do 15 | let(:left) { Relation::Base.new(relation_name, header, body) } 16 | let(:right) { Relation::Base.new(relation_name, header, body) } 17 | 18 | it { should equal(relation) } 19 | end 20 | 21 | context 'when the left and right headers are sorted in different order' do 22 | let(:left) { Relation::Base.new(relation_name, header, body) } 23 | let(:right) { Relation::Base.new(relation_name, header.reverse, body) } 24 | 25 | it { should_not equal(relation) } 26 | 27 | its(:right) { should_not equal(right) } 28 | 29 | it { should be_kind_of(relation.class) } 30 | 31 | its(:left) { should equal(left) } 32 | 33 | its(:right) { should eql(right.project(header)) } 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/set/to_s_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation::Set, '#to_s' do 6 | subject { object.to_s } 7 | 8 | let(:id) { Attribute::Integer.new(:id) } 9 | let(:name) { Attribute::String.new(:name) } 10 | let(:age) { Attribute::Integer.new(:age, required: false) } 11 | let(:header) { [id, name, age] } 12 | let(:body) { [[1, 'Dan Kubb', 35]].each } 13 | let(:base_relation) { Relation::Base.new('users', header, body) } 14 | let(:object) { described_class.new } 15 | 16 | context 'when no object visited' do 17 | it_should_behave_like 'an idempotent method' 18 | 19 | it { should respond_to(:to_s) } 20 | 21 | it { should be_frozen } 22 | 23 | its(:to_s) { should == '' } 24 | end 25 | 26 | context 'when a difference is visited' do 27 | before do 28 | object.visit(base_relation.difference(base_relation)) 29 | end 30 | 31 | it_should_behave_like 'a generated SQL expression' 32 | 33 | its(:to_s) { should eql('(SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")') } 34 | end 35 | 36 | context 'when an intersection is visited' do 37 | before do 38 | object.visit(base_relation.intersect(base_relation)) 39 | end 40 | 41 | it_should_behave_like 'a generated SQL expression' 42 | 43 | its(:to_s) { should eql('(SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")') } 44 | end 45 | 46 | context 'when a union is visited' do 47 | before do 48 | object.visit(base_relation.union(base_relation)) 49 | end 50 | 51 | it_should_behave_like 'a generated SQL expression' 52 | 53 | its(:to_s) { should eql('(SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")') } 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/set/to_subquery_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation::Set, '#to_subquery' do 6 | subject { object.to_subquery } 7 | 8 | let(:id) { Attribute::Integer.new(:id) } 9 | let(:name) { Attribute::String.new(:name) } 10 | let(:age) { Attribute::Integer.new(:age, required: false) } 11 | let(:header) { [id, name, age] } 12 | let(:body) { [[1, 'Dan Kubb', 35]].each } 13 | let(:base_relation) { Relation::Base.new('users', header, body) } 14 | let(:object) { described_class.new } 15 | 16 | context 'when no object visited' do 17 | it_should_behave_like 'an idempotent method' 18 | 19 | it { should respond_to(:to_s) } 20 | 21 | it { should be_frozen } 22 | 23 | its(:to_s) { should == '' } 24 | end 25 | 26 | context 'when a difference is visited' do 27 | before do 28 | object.visit(base_relation.difference(base_relation)) 29 | end 30 | 31 | it_should_behave_like 'a generated SQL expression' 32 | 33 | its(:to_s) { should eql('((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users"))') } 34 | end 35 | 36 | context 'when an intersection is visited' do 37 | before do 38 | object.visit(base_relation.intersect(base_relation)) 39 | end 40 | 41 | it_should_behave_like 'a generated SQL expression' 42 | 43 | its(:to_s) { should eql('((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users"))') } 44 | end 45 | 46 | context 'when a union is visited' do 47 | before do 48 | object.visit(base_relation.union(base_relation)) 49 | end 50 | 51 | it_should_behave_like 'a generated SQL expression' 52 | 53 | its(:to_s) { should eql('((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users"))') } 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/to_s_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation, '#to_s' do 6 | subject { object.to_s } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Relation) } 9 | let(:id) { Attribute::Integer.new(:id) } 10 | let(:name) { Attribute::String.new(:name) } 11 | let(:age) { Attribute::Integer.new(:age, required: false) } 12 | let(:header) { [id, name, age] } 13 | let(:body) { [[1, 'Dan Kubb', 35]].each } 14 | let(:base_relation) { Relation::Base.new('users', header, body) } 15 | let(:object) { described_class.new } 16 | 17 | context 'when no object visited' do 18 | it_should_behave_like 'an idempotent method' 19 | 20 | it { should respond_to(:to_s) } 21 | 22 | it { should be_frozen } 23 | 24 | its(:to_s) { should == '' } 25 | end 26 | 27 | context 'when an object is visited' do 28 | let(:visitable) { double('Visitable') } 29 | 30 | before do 31 | described_class.class_eval do 32 | def visit_rspec_mocks_mock(mock) 33 | @name = mock.instance_variable_get(:@name) 34 | @distinct = 'DISTINCT' 35 | @scope = Set.new 36 | end 37 | 38 | def generate_sql(columns) 39 | "SELECT #{columns} FROM #{@name}" 40 | end 41 | end 42 | 43 | object.visit(visitable) 44 | end 45 | 46 | it_should_behave_like 'a generated SQL expression' 47 | 48 | its(:to_s) { should eql('SELECT DISTINCT FROM Visitable') } 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/to_sql_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation, '#to_sql' do 6 | subject { object.to_sql } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Relation) } 9 | let(:id) { Attribute::Integer.new(:id) } 10 | let(:name) { Attribute::String.new(:name) } 11 | let(:age) { Attribute::Integer.new(:age, required: false) } 12 | let(:header) { [id, name, age] } 13 | let(:body) { [[1, 'Dan Kubb', 35]].each } 14 | let(:object) { described_class.new } 15 | 16 | context 'when no object visited' do 17 | it_should_behave_like 'an idempotent method' 18 | 19 | it { should be_kind_of(String) } 20 | 21 | it { should be_frozen } 22 | 23 | it { should == '' } 24 | end 25 | 26 | context 'when an object is visited' do 27 | let(:visitable) { double('Visitable') } 28 | 29 | before do 30 | described_class.class_eval do 31 | def visit_rspec_mocks_mock(mock) 32 | mock 33 | end 34 | end 35 | end 36 | 37 | before do 38 | @original = object.to_sql 39 | object.visit(visitable) 40 | end 41 | 42 | it_should_behave_like 'an idempotent method' 43 | 44 | it { should be_kind_of(String) } 45 | 46 | it { should be_frozen } 47 | 48 | it { should_not equal(@original) } 49 | 50 | it { should eql(visitable.to_s) } 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/to_subquery_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation, '#to_subquery' do 6 | subject { object.to_subquery } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Relation) } 9 | let(:id) { Attribute::Integer.new(:id) } 10 | let(:name) { Attribute::String.new(:name) } 11 | let(:age) { Attribute::Integer.new(:age, required: false) } 12 | let(:header) { [id, name, age] } 13 | let(:body) { [[1, 'Dan Kubb', 35]].each } 14 | let(:base_relation) { Relation::Base.new('users', header, body) } 15 | let(:object) { described_class.new } 16 | 17 | context 'when no object visited' do 18 | it_should_behave_like 'an idempotent method' 19 | 20 | it { should respond_to(:to_s) } 21 | 22 | it { should be_frozen } 23 | 24 | its(:to_s) { should == '' } 25 | end 26 | 27 | context 'when an object is visited' do 28 | let(:visitable) { double('Visitable') } 29 | 30 | before do 31 | described_class.class_eval do 32 | def visit_rspec_mocks_mock(mock) 33 | @name = mock.instance_variable_get(:@name) 34 | @scope = Set.new 35 | end 36 | 37 | def generate_sql(columns) 38 | "SELECT #{columns} FROM #{@name}" 39 | end 40 | end 41 | 42 | object.visit(visitable) 43 | end 44 | 45 | it_should_behave_like 'a generated SQL expression' 46 | 47 | its(:to_s) { should eql('(SELECT * FROM Visitable)') } 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/unary/to_s_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation::Unary, '#to_s' do 6 | subject { object.to_s } 7 | 8 | let(:id) { Attribute::Integer.new(:id) } 9 | let(:name) { Attribute::String.new(:name) } 10 | let(:age) { Attribute::Integer.new(:age, required: false) } 11 | let(:header) { [id, name, age] } 12 | let(:body) { [[1, 'Dan Kubb', 35]].each } 13 | let(:base_relation) { Relation::Base.new('users', header, body) } 14 | let(:object) { described_class.new } 15 | 16 | context 'when no object visited' do 17 | it_should_behave_like 'an idempotent method' 18 | 19 | it { should respond_to(:to_s) } 20 | 21 | it { should be_frozen } 22 | 23 | its(:to_s) { should == '' } 24 | end 25 | 26 | context 'when a restriction is visited' do 27 | before do 28 | object.visit(base_relation.restrict { |r| r.id.eq(1) }) 29 | end 30 | 31 | it_should_behave_like 'a generated SQL expression' 32 | 33 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" WHERE "id" = 1') } 34 | end 35 | 36 | context 'when a limit is visited' do 37 | before do 38 | object.visit(base_relation.sort_by { |r| [r.id, r.name, r.age] }.take(1)) 39 | end 40 | 41 | it_should_behave_like 'a generated SQL expression' 42 | 43 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1') } 44 | end 45 | 46 | context 'when an offset is visited' do 47 | before do 48 | object.visit(base_relation.sort_by { |r| [r.id, r.name, r.age] }.drop(1)) 49 | end 50 | 51 | it_should_behave_like 'a generated SQL expression' 52 | 53 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1') } 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/unary/to_subquery_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation::Unary, '#to_subquery' do 6 | subject { object.to_subquery } 7 | 8 | let(:id) { Attribute::Integer.new(:id) } 9 | let(:name) { Attribute::String.new(:name) } 10 | let(:age) { Attribute::Integer.new(:age, required: false) } 11 | let(:header) { [id, name, age] } 12 | let(:body) { [[1, 'Dan Kubb', 35]].each } 13 | let(:base_relation) { Relation::Base.new('users', header, body) } 14 | let(:object) { described_class.new } 15 | 16 | context 'when no object visited' do 17 | it_should_behave_like 'an idempotent method' 18 | 19 | it { should respond_to(:to_s) } 20 | 21 | it { should be_frozen } 22 | 23 | its(:to_s) { should == '' } 24 | end 25 | 26 | context 'when a projection is visited' do 27 | before do 28 | object.visit(base_relation.project([:id, :name])) 29 | end 30 | 31 | it_should_behave_like 'a generated SQL expression' 32 | 33 | its(:to_s) { should eql('(SELECT DISTINCT "id", "name" FROM "users")') } 34 | end 35 | 36 | context 'when a rename is visited' do 37 | before do 38 | object.visit(base_relation.rename(id: :user_id)) 39 | end 40 | 41 | it_should_behave_like 'a generated SQL expression' 42 | 43 | its(:to_s) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users")') } 44 | end 45 | 46 | context 'when a restriction is visited' do 47 | before do 48 | object.visit(base_relation.restrict { |r| r.id.eq(1) }) 49 | end 50 | 51 | it_should_behave_like 'a generated SQL expression' 52 | 53 | its(:to_s) { should eql('(SELECT * FROM "users" WHERE "id" = 1)') } 54 | end 55 | 56 | context 'when a limit is visited' do 57 | before do 58 | object.visit(base_relation.sort_by { |r| [r.id, r.name, r.age] }.take(1)) 59 | end 60 | 61 | it_should_behave_like 'a generated SQL expression' 62 | 63 | its(:to_s) { should eql('(SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1)') } 64 | end 65 | 66 | context 'when an offset is visited' do 67 | before do 68 | object.visit(base_relation.sort_by { |r| [r.id, r.name, r.age] }.drop(1)) 69 | end 70 | 71 | it_should_behave_like 'a generated SQL expression' 72 | 73 | its(:to_s) { should eql('(SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1)') } 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/unary/visit_axiom_algebra_extension_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation::Unary, '#visit_axiom_algebra_extension' do 6 | subject { object.visit_axiom_algebra_extension(extension) } 7 | 8 | let(:relation_name) { 'users' } 9 | let(:id) { Attribute::Integer.new(:id) } 10 | let(:name) { Attribute::String.new(:name) } 11 | let(:age) { Attribute::Integer.new(:age, required: false) } 12 | let(:header) { [id, name, age] } 13 | let(:body) { [[1, 'Dan Kubb', 35]].each } 14 | let(:base_relation) { Relation::Base.new(relation_name, header, body) } 15 | let(:extension) { operand.extend { |r| r.add(:one, 1) } } 16 | let(:object) { described_class.new } 17 | 18 | context 'when the operand is a base relation' do 19 | let(:operand) { base_relation } 20 | 21 | it_should_behave_like 'a generated SQL SELECT query' 22 | 23 | its(:to_s) { should eql('SELECT "id", "name", "age", 1 AS "one" FROM "users"') } 24 | its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM "users")') } 25 | end 26 | 27 | context 'when the operand is a projection' do 28 | let(:operand) { base_relation.project([:id, :name]) } 29 | 30 | it_should_behave_like 'a generated SQL SELECT query' 31 | 32 | its(:to_s) { should eql('SELECT DISTINCT "id", "name", 1 AS "one" FROM "users"') } 33 | its(:to_subquery) { should eql('(SELECT DISTINCT "id", "name", 1 AS "one" FROM "users")') } 34 | end 35 | 36 | context 'when the operand is an extension' do 37 | let(:operand) { base_relation.extend { |r| r.add(:two, 2) } } 38 | 39 | it_should_behave_like 'a generated SQL SELECT query' 40 | 41 | its(:to_s) { should eql('SELECT "id", "name", "age", "two", 1 AS "one" FROM (SELECT *, 2 AS "two" FROM "users") AS "users"') } 42 | its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM (SELECT *, 2 AS "two" FROM "users") AS "users")') } 43 | end 44 | 45 | context 'when the operand is a rename' do 46 | let(:operand) { base_relation.rename(id: :user_id) } 47 | 48 | it_should_behave_like 'a generated SQL SELECT query' 49 | 50 | its(:to_s) { should eql('SELECT "user_id", "name", "age", 1 AS "one" FROM (SELECT "id" AS "user_id", "name", "age" FROM "users") AS "users"') } 51 | its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM (SELECT "id" AS "user_id", "name", "age" FROM "users") AS "users")') } 52 | end 53 | 54 | context 'when the operand is a restriction' do 55 | let(:operand) { base_relation.restrict { |r| r.id.eq(1) } } 56 | 57 | it_should_behave_like 'a generated SQL SELECT query' 58 | 59 | its(:to_s) { should eql('SELECT "id", "name", "age", 1 AS "one" FROM "users" WHERE "id" = 1') } 60 | its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM "users" WHERE "id" = 1)') } 61 | end 62 | 63 | context 'when the operand is a summarization' do 64 | context 'summarize per table dee' do 65 | let(:summarize_per) { TABLE_DEE } 66 | let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) } } 67 | 68 | it_should_behave_like 'a generated SQL SELECT query' 69 | 70 | its(:to_s) { should eql('SELECT "count", 1 AS "one" FROM (SELECT COUNT ("id") AS "count" FROM "users") AS "users"') } 71 | its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM (SELECT COUNT ("id") AS "count" FROM "users") AS "users")') } 72 | end 73 | 74 | context 'summarize per table dum' do 75 | let(:summarize_per) { TABLE_DUM } 76 | let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) } } 77 | 78 | it_should_behave_like 'a generated SQL SELECT query' 79 | 80 | its(:to_s) { should eql('SELECT "count", 1 AS "one" FROM (SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE) AS "users"') } 81 | its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM (SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE) AS "users")') } 82 | end 83 | 84 | context 'summarize by a subset of the operand header' do 85 | let(:operand) { base_relation.summarize([:id, :name]) { |r| r.add(:count, r.age.count) } } 86 | 87 | it_should_behave_like 'a generated SQL SELECT query' 88 | 89 | its(:to_s) { should eql('SELECT "id", "name", "count", 1 AS "one" FROM (SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0) AS "users"') } 90 | its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM (SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0) AS "users")') } 91 | end 92 | end 93 | 94 | context 'when the operand is sorted' do 95 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] } } 96 | 97 | it_should_behave_like 'a generated SQL SELECT query' 98 | 99 | its(:to_s) { should eql('SELECT "id", "name", "age", 1 AS "one" FROM "users" ORDER BY "id", "name", "age"') } 100 | its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM "users" ORDER BY "id", "name", "age")') } 101 | end 102 | 103 | context 'when the operand is reversed' do 104 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] }.reverse } 105 | 106 | it_should_behave_like 'a generated SQL SELECT query' 107 | 108 | its(:to_s) { should eql('SELECT "id", "name", "age", 1 AS "one" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC') } 109 | its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC)') } 110 | end 111 | 112 | context 'when the operand is limited' do 113 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] }.take(1) } 114 | 115 | it_should_behave_like 'a generated SQL SELECT query' 116 | 117 | its(:to_s) { should eql('SELECT "id", "name", "age", 1 AS "one" FROM "users" ORDER BY "id", "name", "age" LIMIT 1') } 118 | its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM "users" ORDER BY "id", "name", "age" LIMIT 1)') } 119 | end 120 | 121 | context 'when the operand is an offset' do 122 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] }.drop(1) } 123 | 124 | it_should_behave_like 'a generated SQL SELECT query' 125 | 126 | its(:to_s) { should eql('SELECT "id", "name", "age", 1 AS "one" FROM "users" ORDER BY "id", "name", "age" OFFSET 1') } 127 | its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM "users" ORDER BY "id", "name", "age" OFFSET 1)') } 128 | end 129 | 130 | context 'when the operand is a difference' do 131 | let(:operand) { base_relation.difference(base_relation) } 132 | 133 | it_should_behave_like 'a generated SQL SELECT query' 134 | 135 | its(:to_s) { should eql('SELECT "id", "name", "age", 1 AS "one" FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users"') } 136 | its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users")') } 137 | end 138 | 139 | context 'when the operand is an intersection' do 140 | let(:operand) { base_relation.intersect(base_relation) } 141 | 142 | it_should_behave_like 'a generated SQL SELECT query' 143 | 144 | its(:to_s) { should eql('SELECT "id", "name", "age", 1 AS "one" FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users"') } 145 | its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users")') } 146 | end 147 | 148 | context 'when the operand is a union' do 149 | let(:operand) { base_relation.union(base_relation) } 150 | 151 | it_should_behave_like 'a generated SQL SELECT query' 152 | 153 | its(:to_s) { should eql('SELECT "id", "name", "age", 1 AS "one" FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users"') } 154 | its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users")') } 155 | end 156 | 157 | context 'when the operand is a join' do 158 | let(:operand) { base_relation.join(base_relation) } 159 | 160 | it_should_behave_like 'a generated SQL SELECT query' 161 | 162 | its(:to_s) { should eql('SELECT "id", "name", "age", 1 AS "one" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users"') } 163 | its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users")') } 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/unary/visit_axiom_algebra_rename_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation::Unary, '#visit_axiom_algebra_rename' do 6 | subject { object.visit_axiom_algebra_rename(rename) } 7 | 8 | let(:relation_name) { 'users' } 9 | let(:id) { Attribute::Integer.new(:id) } 10 | let(:name) { Attribute::String.new(:name) } 11 | let(:age) { Attribute::Integer.new(:age, required: false) } 12 | let(:header) { [id, name, age] } 13 | let(:body) { [[1, 'Dan Kubb', 35]].each } 14 | let(:base_relation) { Relation::Base.new(relation_name, header, body) } 15 | let(:rename) { operand.rename(id: :user_id) } 16 | let(:object) { described_class.new } 17 | 18 | context 'when the operand is a base relation' do 19 | let(:operand) { base_relation } 20 | 21 | it_should_behave_like 'a generated SQL SELECT query' 22 | 23 | its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users"') } 24 | its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users")') } 25 | end 26 | 27 | context 'when the operand is a projection' do 28 | let(:operand) { base_relation.project([:id, :name]) } 29 | 30 | it_should_behave_like 'a generated SQL SELECT query' 31 | 32 | its(:to_s) { should eql('SELECT DISTINCT "id" AS "user_id", "name" FROM "users"') } 33 | its(:to_subquery) { should eql('(SELECT DISTINCT "id" AS "user_id", "name" FROM "users")') } 34 | end 35 | 36 | context 'when the operand is an extension' do 37 | let(:operand) { base_relation.extend { |r| r.add(:one, 1) } } 38 | 39 | it_should_behave_like 'a generated SQL SELECT query' 40 | 41 | its(:to_s) { pending { should eql('SELECT "id" AS "user_id", "name", "age", 1 AS "one" FROM "users"') } } 42 | its(:to_subquery) { pending { should eql('SELECT "id" AS "user_id", "name", "age", 1 AS "one" FROM "users"') } } 43 | end 44 | 45 | context 'when the operand is a rename' do 46 | let(:operand) { base_relation.rename(name: :other_name) } 47 | 48 | context 'when the relation is not optimized' do 49 | it_should_behave_like 'a generated SQL SELECT query' 50 | 51 | its(:to_s) { should eql('SELECT "id" AS "user_id", "other_name", "age" FROM (SELECT "id", "name" AS "other_name", "age" FROM "users") AS "users"') } 52 | its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "other_name", "age" FROM (SELECT "id", "name" AS "other_name", "age" FROM "users") AS "users")') } 53 | end 54 | 55 | context 'when the operand is empty' do 56 | let(:operand) { base_relation.rename({}) } 57 | 58 | it_should_behave_like 'a generated SQL SELECT query' 59 | 60 | its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM (SELECT "id", "name", "age" FROM "users") AS "users"') } 61 | its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM (SELECT "id", "name", "age" FROM "users") AS "users")') } 62 | end 63 | end 64 | 65 | context 'when the operand is a restriction' do 66 | let(:operand) { base_relation.restrict { |r| r.id.eq(1) } } 67 | 68 | it_should_behave_like 'a generated SQL SELECT query' 69 | 70 | its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users" WHERE "id" = 1') } 71 | its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users" WHERE "id" = 1)') } 72 | end 73 | 74 | context 'when the operand is a summarization' do 75 | context 'summarize per table dee' do 76 | let(:summarize_per) { TABLE_DEE } 77 | let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) } } 78 | let(:rename) { operand.rename(count: :other_count) } 79 | 80 | it_should_behave_like 'a generated SQL SELECT query' 81 | 82 | its(:to_s) { should eql('SELECT "count" AS "other_count" FROM (SELECT COUNT ("id") AS "count" FROM "users") AS "users"') } 83 | its(:to_subquery) { should eql('(SELECT "count" AS "other_count" FROM (SELECT COUNT ("id") AS "count" FROM "users") AS "users")') } 84 | end 85 | 86 | context 'summarize per table dum' do 87 | let(:summarize_per) { TABLE_DUM } 88 | let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) } } 89 | let(:rename) { operand.rename(count: :other_count) } 90 | 91 | it_should_behave_like 'a generated SQL SELECT query' 92 | 93 | its(:to_s) { should eql('SELECT "count" AS "other_count" FROM (SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE) AS "users"') } 94 | its(:to_subquery) { should eql('(SELECT "count" AS "other_count" FROM (SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE) AS "users")') } 95 | end 96 | 97 | context 'summarize by a subset of the operand header' do 98 | let(:operand) { base_relation.summarize([:id, :name]) { |r| r.add(:count, r.age.count) } } 99 | 100 | it_should_behave_like 'a generated SQL SELECT query' 101 | 102 | its(:to_s) { pending { should eql('SELECT "id" AS "user_id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name"') } } 103 | its(:to_subquery) { pending { should eql('SELECT "id" AS "user_id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name"') } } 104 | end 105 | end 106 | 107 | context 'when the operand is sorted' do 108 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] } } 109 | 110 | it_should_behave_like 'a generated SQL SELECT query' 111 | 112 | its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age"') } 113 | its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age")') } 114 | end 115 | 116 | context 'when the operand is reversed' do 117 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] }.reverse } 118 | 119 | it_should_behave_like 'a generated SQL SELECT query' 120 | 121 | its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC') } 122 | its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC)') } 123 | end 124 | 125 | context 'when the operand is limited' do 126 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] }.take(1) } 127 | 128 | it_should_behave_like 'a generated SQL SELECT query' 129 | 130 | its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1') } 131 | its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1)') } 132 | end 133 | 134 | context 'when the operand is an offset' do 135 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] }.drop(1) } 136 | 137 | it_should_behave_like 'a generated SQL SELECT query' 138 | 139 | its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1') } 140 | its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1)') } 141 | end 142 | 143 | context 'when the operand is a difference' do 144 | let(:operand) { base_relation.difference(base_relation) } 145 | 146 | it_should_behave_like 'a generated SQL SELECT query' 147 | 148 | its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users"') } 149 | its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users")') } 150 | end 151 | 152 | context 'when the operand is an intersection' do 153 | let(:operand) { base_relation.intersect(base_relation) } 154 | 155 | it_should_behave_like 'a generated SQL SELECT query' 156 | 157 | its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users"') } 158 | its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users")') } 159 | end 160 | 161 | context 'when the operand is a union' do 162 | let(:operand) { base_relation.union(base_relation) } 163 | 164 | it_should_behave_like 'a generated SQL SELECT query' 165 | 166 | its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users"') } 167 | its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users")') } 168 | end 169 | 170 | context 'when the operand is a join' do 171 | let(:operand) { base_relation.join(base_relation) } 172 | 173 | it_should_behave_like 'a generated SQL SELECT query' 174 | 175 | its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users"') } 176 | its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users")') } 177 | end 178 | end 179 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/unary/visit_axiom_relation_base_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation::Unary, '#visit_axiom_relation_base' do 6 | subject { object.visit_axiom_relation_base(base_relation) } 7 | 8 | let(:relation_name) { 'users' } 9 | let(:id) { Attribute::Integer.new(:id) } 10 | let(:name) { Attribute::String.new(:name) } 11 | let(:age) { Attribute::Integer.new(:age, required: false) } 12 | let(:header) { [id, name, age] } 13 | let(:body) { [[1, 'Dan Kubb', 35]].each } 14 | let(:base_relation) { Relation::Base.new(relation_name, header, body) } 15 | let(:object) { described_class.new } 16 | 17 | it_should_behave_like 'a generated SQL SELECT query' 18 | 19 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users"') } 20 | its(:to_subquery) { should eql('(SELECT * FROM "users")') } 21 | end 22 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/unary/visit_axiom_relation_operation_limit_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation::Unary, '#visit_axiom_relation_operation_limit' do 6 | subject { object.visit_axiom_relation_operation_limit(limit) } 7 | 8 | let(:relation_name) { 'users' } 9 | let(:id) { Attribute::Integer.new(:id) } 10 | let(:name) { Attribute::String.new(:name) } 11 | let(:age) { Attribute::Integer.new(:age, required: false) } 12 | let(:header) { [id, name, age] } 13 | let(:body) { [[1, 'Dan Kubb', 35]].each } 14 | let(:base_relation) { Relation::Base.new(relation_name, header, body) } 15 | let(:limit) { operand.take(1) } 16 | let(:object) { described_class.new } 17 | 18 | context 'when the operand is a base relation' do 19 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] } } 20 | 21 | it_should_behave_like 'a generated SQL SELECT query' 22 | 23 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1') } 24 | its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1)') } 25 | end 26 | 27 | context 'when the operand is a projection' do 28 | let(:operand) { base_relation.project([:id, :name]).sort_by { |r| [r.id, r.name] } } 29 | 30 | it_should_behave_like 'a generated SQL SELECT query' 31 | 32 | its(:to_s) { should eql('SELECT DISTINCT "id", "name" FROM "users" ORDER BY "id", "name" LIMIT 1') } 33 | its(:to_subquery) { should eql('(SELECT DISTINCT "id", "name" FROM "users" ORDER BY "id", "name" LIMIT 1)') } 34 | end 35 | 36 | context 'when the operand is an extension' do 37 | let(:operand) { base_relation.extend { |r| r.add(:one, 1) }.sort_by { |r| [r.id, r.name, r.age, r.one] } } 38 | 39 | it_should_behave_like 'a generated SQL SELECT query' 40 | 41 | its(:to_s) { should eql('SELECT "id", "name", "age", 1 AS "one" FROM "users" ORDER BY "id", "name", "age", "one" LIMIT 1') } 42 | its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM "users" ORDER BY "id", "name", "age", "one" LIMIT 1)') } 43 | end 44 | 45 | context 'when the operand is a rename' do 46 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] }.rename(id: :user_id) } 47 | 48 | it_should_behave_like 'a generated SQL SELECT query' 49 | 50 | its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1') } 51 | its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1)') } 52 | end 53 | 54 | context 'when the operand is a restriction' do 55 | let(:operand) { base_relation.restrict { |r| r.id.eq(1) }.sort_by { |r| [r.id, r.name, r.age] } } 56 | 57 | it_should_behave_like 'a generated SQL SELECT query' 58 | 59 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" WHERE "id" = 1 ORDER BY "id", "name", "age" LIMIT 1') } 60 | its(:to_subquery) { should eql('(SELECT * FROM "users" WHERE "id" = 1 ORDER BY "id", "name", "age" LIMIT 1)') } 61 | end 62 | 63 | context 'when the operand is a summarization' do 64 | context 'summarize per table dee' do 65 | let(:summarize_per) { TABLE_DEE } 66 | let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) }.sort_by { |r| r.count } } 67 | 68 | it_should_behave_like 'a generated SQL SELECT query' 69 | 70 | its(:to_s) { should eql('SELECT COUNT ("id") AS "count" FROM "users" ORDER BY "count" LIMIT 1') } 71 | its(:to_subquery) { should eql('(SELECT COUNT ("id") AS "count" FROM "users" ORDER BY "count" LIMIT 1)') } 72 | end 73 | 74 | context 'summarize per table dum' do 75 | let(:summarize_per) { TABLE_DUM } 76 | let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) }.sort_by { |r| r.count } } 77 | 78 | it_should_behave_like 'a generated SQL SELECT query' 79 | 80 | its(:to_s) { should eql('SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE ORDER BY "count" LIMIT 1') } 81 | its(:to_subquery) { should eql('(SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE ORDER BY "count" LIMIT 1)') } 82 | end 83 | 84 | context 'summarize by a subset of the operand header' do 85 | let(:operand) { base_relation.summarize([:id, :name]) { |r| r.add(:count, r.age.count) }.sort_by { |r| [r.id, r.name, r.count] } } 86 | 87 | it_should_behave_like 'a generated SQL SELECT query' 88 | 89 | its(:to_s) { should eql('SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0 ORDER BY "id", "name", "count" LIMIT 1') } 90 | its(:to_subquery) { should eql('(SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0 ORDER BY "id", "name", "count" LIMIT 1)') } 91 | end 92 | end 93 | 94 | context 'when the operand is sorted' do 95 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] } } 96 | 97 | it_should_behave_like 'a generated SQL SELECT query' 98 | 99 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1') } 100 | its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1)') } 101 | end 102 | 103 | context 'when the operand is reversed' do 104 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] }.reverse } 105 | 106 | it_should_behave_like 'a generated SQL SELECT query' 107 | 108 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC LIMIT 1') } 109 | its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC LIMIT 1)') } 110 | end 111 | 112 | context 'when the operand is limited' do 113 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] }.take(1) } 114 | 115 | it_should_behave_like 'a generated SQL SELECT query' 116 | 117 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" LIMIT 1') } 118 | its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" LIMIT 1)') } 119 | end 120 | 121 | context 'when the operand is an offset' do 122 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] }.drop(1) } 123 | 124 | it_should_behave_like 'a generated SQL SELECT query' 125 | 126 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" LIMIT 1 OFFSET 1') } 127 | its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1 OFFSET 1)') } 128 | end 129 | 130 | context 'when the operand is a difference' do 131 | let(:operand) { base_relation.difference(base_relation).sort_by { |r| [r.id, r.name, r.age] } } 132 | 133 | it_should_behave_like 'a generated SQL SELECT query' 134 | 135 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age" LIMIT 1') } 136 | its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age" LIMIT 1)') } 137 | end 138 | 139 | context 'when the operand is an intersection' do 140 | let(:operand) { base_relation.intersect(base_relation).sort_by { |r| [r.id, r.name, r.age] } } 141 | 142 | it_should_behave_like 'a generated SQL SELECT query' 143 | 144 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age" LIMIT 1') } 145 | its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age" LIMIT 1)') } 146 | end 147 | 148 | context 'when the operand is a union' do 149 | let(:operand) { base_relation.union(base_relation).sort_by { |r| [r.id, r.name, r.age] } } 150 | 151 | it_should_behave_like 'a generated SQL SELECT query' 152 | 153 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age" LIMIT 1') } 154 | its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age" LIMIT 1)') } 155 | end 156 | 157 | context 'when the operand is a join' do 158 | let(:operand) { base_relation.join(base_relation).sort_by { |r| [r.id, r.name, r.age] } } 159 | 160 | it_should_behave_like 'a generated SQL SELECT query' 161 | 162 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" ORDER BY "id", "name", "age" LIMIT 1') } 163 | its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" ORDER BY "id", "name", "age" LIMIT 1)') } 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/unary/visit_axiom_relation_operation_offset_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation::Unary, '#visit_axiom_relation_operation_offset' do 6 | subject { object.visit_axiom_relation_operation_offset(offset) } 7 | 8 | let(:relation_name) { 'users' } 9 | let(:id) { Attribute::Integer.new(:id) } 10 | let(:name) { Attribute::String.new(:name) } 11 | let(:age) { Attribute::Integer.new(:age, required: false) } 12 | let(:header) { [id, name, age] } 13 | let(:body) { [[1, 'Dan Kubb', 35]].each } 14 | let(:base_relation) { Relation::Base.new(relation_name, header, body) } 15 | let(:offset) { operand.drop(1) } 16 | let(:object) { described_class.new } 17 | 18 | context 'when the operand is a base relation' do 19 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] } } 20 | 21 | it_should_behave_like 'a generated SQL SELECT query' 22 | 23 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1') } 24 | its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1)') } 25 | end 26 | 27 | context 'when the operand is a projection' do 28 | let(:operand) { base_relation.project([:id, :name]).sort_by { |r| [r.id, r.name] } } 29 | 30 | it_should_behave_like 'a generated SQL SELECT query' 31 | 32 | its(:to_s) { should eql('SELECT DISTINCT "id", "name" FROM "users" ORDER BY "id", "name" OFFSET 1') } 33 | its(:to_subquery) { should eql('(SELECT DISTINCT "id", "name" FROM "users" ORDER BY "id", "name" OFFSET 1)') } 34 | end 35 | 36 | context 'when the operand is an extension' do 37 | let(:operand) { base_relation.extend { |r| r.add(:one, 1) }.sort_by { |r| [r.id, r.name, r.age, r.one] } } 38 | 39 | it_should_behave_like 'a generated SQL SELECT query' 40 | 41 | its(:to_s) { should eql('SELECT "id", "name", "age", 1 AS "one" FROM "users" ORDER BY "id", "name", "age", "one" OFFSET 1') } 42 | its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM "users" ORDER BY "id", "name", "age", "one" OFFSET 1)') } 43 | end 44 | 45 | context 'when the operand is a rename' do 46 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] }.rename(id: :user_id) } 47 | 48 | it_should_behave_like 'a generated SQL SELECT query' 49 | 50 | its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1') } 51 | its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1)') } 52 | end 53 | 54 | context 'when the operand is a restriction' do 55 | let(:operand) { base_relation.restrict { |r| r.id.eq(1) }.sort_by { |r| [r.id, r.name, r.age] } } 56 | 57 | it_should_behave_like 'a generated SQL SELECT query' 58 | 59 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" WHERE "id" = 1 ORDER BY "id", "name", "age" OFFSET 1') } 60 | its(:to_subquery) { should eql('(SELECT * FROM "users" WHERE "id" = 1 ORDER BY "id", "name", "age" OFFSET 1)') } 61 | end 62 | 63 | context 'when the operand is a summarization' do 64 | context 'summarize per table dee' do 65 | let(:summarize_per) { TABLE_DEE } 66 | let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) }.sort_by { |r| r.count } } 67 | 68 | it_should_behave_like 'a generated SQL SELECT query' 69 | 70 | its(:to_s) { should eql('SELECT COUNT ("id") AS "count" FROM "users" ORDER BY "count" OFFSET 1') } 71 | its(:to_subquery) { should eql('(SELECT COUNT ("id") AS "count" FROM "users" ORDER BY "count" OFFSET 1)') } 72 | end 73 | 74 | context 'summarize per table dum' do 75 | let(:summarize_per) { TABLE_DUM } 76 | let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) }.sort_by { |r| r.count } } 77 | 78 | it_should_behave_like 'a generated SQL SELECT query' 79 | 80 | its(:to_s) { should eql('SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE ORDER BY "count" OFFSET 1') } 81 | its(:to_subquery) { should eql('(SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE ORDER BY "count" OFFSET 1)') } 82 | end 83 | 84 | context 'summarize by a subset of the operand header' do 85 | let(:operand) { base_relation.summarize([:id, :name]) { |r| r.add(:count, r.age.count) }.sort_by { |r| [r.id, r.name, r.count] } } 86 | 87 | it_should_behave_like 'a generated SQL SELECT query' 88 | 89 | its(:to_s) { should eql('SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0 ORDER BY "id", "name", "count" OFFSET 1') } 90 | its(:to_subquery) { should eql('(SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0 ORDER BY "id", "name", "count" OFFSET 1)') } 91 | end 92 | end 93 | 94 | context 'when the operand is sorted' do 95 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] } } 96 | 97 | it_should_behave_like 'a generated SQL SELECT query' 98 | 99 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age" OFFSET 1') } 100 | its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1)') } 101 | end 102 | 103 | context 'when the operand is reversed' do 104 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] }.reverse } 105 | 106 | it_should_behave_like 'a generated SQL SELECT query' 107 | 108 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC OFFSET 1') } 109 | its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC OFFSET 1)') } 110 | end 111 | 112 | context 'when the operand is limited' do 113 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] }.take(1) } 114 | 115 | it_should_behave_like 'a generated SQL SELECT query' 116 | 117 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" OFFSET 1') } 118 | its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" OFFSET 1)') } 119 | end 120 | 121 | context 'when the operand is an offset' do 122 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] }.drop(1) } 123 | 124 | it_should_behave_like 'a generated SQL SELECT query' 125 | 126 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "users" OFFSET 1') } 127 | its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "users" OFFSET 1)') } 128 | end 129 | 130 | context 'when the operand is a difference' do 131 | let(:operand) { base_relation.difference(base_relation).sort_by { |r| [r.id, r.name, r.age] } } 132 | 133 | it_should_behave_like 'a generated SQL SELECT query' 134 | 135 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age" OFFSET 1') } 136 | its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age" OFFSET 1)') } 137 | end 138 | 139 | context 'when the operand is an intersection' do 140 | let(:operand) { base_relation.intersect(base_relation).sort_by { |r| [r.id, r.name, r.age] } } 141 | 142 | it_should_behave_like 'a generated SQL SELECT query' 143 | 144 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age" OFFSET 1') } 145 | its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age" OFFSET 1)') } 146 | end 147 | 148 | context 'when the operand is a union' do 149 | let(:operand) { base_relation.union(base_relation).sort_by { |r| [r.id, r.name, r.age] } } 150 | 151 | it_should_behave_like 'a generated SQL SELECT query' 152 | 153 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age" OFFSET 1') } 154 | its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id", "name", "age" OFFSET 1)') } 155 | end 156 | 157 | context 'when the operand is a join' do 158 | let(:operand) { base_relation.join(base_relation).sort_by { |r| [r.id, r.name, r.age] } } 159 | 160 | it_should_behave_like 'a generated SQL SELECT query' 161 | 162 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" ORDER BY "id", "name", "age" OFFSET 1') } 163 | its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" ORDER BY "id", "name", "age" OFFSET 1)') } 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/unary/visit_axiom_relation_operation_reverse_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation::Unary, '#visit_axiom_relation_operation_reverse' do 6 | subject { object.visit_axiom_relation_operation_reverse(sorted) } 7 | 8 | let(:relation_name) { 'users' } 9 | let(:id) { Attribute::Integer.new(:id) } 10 | let(:name) { Attribute::String.new(:name) } 11 | let(:age) { Attribute::Integer.new(:age, required: false) } 12 | let(:header) { [id, name, age] } 13 | let(:body) { [[1, 'Dan Kubb', 35]].each } 14 | let(:base_relation) { Relation::Base.new(relation_name, header, body) } 15 | let(:sorted) { operand.reverse } 16 | let(:object) { described_class.new } 17 | 18 | context 'when the operand is a base relation' do 19 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] } } 20 | 21 | it_should_behave_like 'a generated SQL SELECT query' 22 | 23 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC') } 24 | its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC)') } 25 | end 26 | 27 | context 'when the operand is a projection' do 28 | let(:operand) { base_relation.project([:id, :name]).sort_by { |r| [r.id, r.name] } } 29 | 30 | it_should_behave_like 'a generated SQL SELECT query' 31 | 32 | its(:to_s) { should eql('SELECT DISTINCT "id", "name" FROM "users" ORDER BY "id" DESC, "name" DESC') } 33 | its(:to_subquery) { should eql('(SELECT DISTINCT "id", "name" FROM "users" ORDER BY "id" DESC, "name" DESC)') } 34 | end 35 | 36 | context 'when the operand is an extension' do 37 | let(:operand) { base_relation.extend { |r| r.add(:one, 1) }.sort_by { |r| [r.id, r.name, r.age, r.one] } } 38 | 39 | it_should_behave_like 'a generated SQL SELECT query' 40 | 41 | its(:to_s) { should eql('SELECT "id", "name", "age", 1 AS "one" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC, "one" DESC') } 42 | its(:to_subquery) { should eql('(SELECT *, 1 AS "one" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC, "one" DESC)') } 43 | end 44 | 45 | context 'when the operand is a rename' do 46 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] }.rename(id: :user_id) } 47 | 48 | it_should_behave_like 'a generated SQL SELECT query' 49 | 50 | its(:to_s) { should eql('SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "user_id" DESC, "name" DESC, "age" DESC') } 51 | its(:to_subquery) { should eql('(SELECT "id" AS "user_id", "name", "age" FROM "users" ORDER BY "user_id" DESC, "name" DESC, "age" DESC)') } 52 | end 53 | 54 | context 'when the operand is a restriction' do 55 | let(:operand) { base_relation.restrict { |r| r.id.eq(1) }.sort_by { |r| [r.id, r.name, r.age] } } 56 | 57 | it_should_behave_like 'a generated SQL SELECT query' 58 | 59 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" WHERE "id" = 1 ORDER BY "id" DESC, "name" DESC, "age" DESC') } 60 | its(:to_subquery) { should eql('(SELECT * FROM "users" WHERE "id" = 1 ORDER BY "id" DESC, "name" DESC, "age" DESC)') } 61 | end 62 | 63 | context 'when the operand is a summarization' do 64 | context 'summarize per table dee' do 65 | let(:summarize_per) { TABLE_DEE } 66 | let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) }.sort_by { |r| r.count } } 67 | 68 | it_should_behave_like 'a generated SQL SELECT query' 69 | 70 | its(:to_s) { should eql('SELECT COUNT ("id") AS "count" FROM "users" ORDER BY "count" DESC') } 71 | its(:to_subquery) { should eql('(SELECT COUNT ("id") AS "count" FROM "users" ORDER BY "count" DESC)') } 72 | end 73 | 74 | context 'summarize per table dum' do 75 | let(:summarize_per) { TABLE_DUM } 76 | let(:operand) { base_relation.summarize(summarize_per) { |r| r.add(:count, r.id.count) }.sort_by { |r| r.count } } 77 | 78 | it_should_behave_like 'a generated SQL SELECT query' 79 | 80 | its(:to_s) { should eql('SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE ORDER BY "count" DESC') } 81 | its(:to_subquery) { should eql('(SELECT COUNT ("id") AS "count" FROM "users" HAVING FALSE ORDER BY "count" DESC)') } 82 | end 83 | 84 | context 'summarize by a subset of the operand header' do 85 | let(:operand) { base_relation.summarize([:id, :name]) { |r| r.add(:count, r.age.count) }.sort_by { |r| [r.id, r.name, r.count] } } 86 | 87 | it_should_behave_like 'a generated SQL SELECT query' 88 | 89 | its(:to_s) { should eql('SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0 ORDER BY "id" DESC, "name" DESC, "count" DESC') } 90 | its(:to_subquery) { should eql('(SELECT "id", "name", COUNT ("age") AS "count" FROM "users" GROUP BY "id", "name" HAVING COUNT (*) > 0 ORDER BY "id" DESC, "name" DESC, "count" DESC)') } 91 | end 92 | end 93 | 94 | context 'when the operand is sorted' do 95 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] } } 96 | 97 | it_should_behave_like 'a generated SQL SELECT query' 98 | 99 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC') } 100 | its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id" DESC, "name" DESC, "age" DESC)') } 101 | end 102 | 103 | context 'when the operand is reversed' do 104 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] }.reverse } 105 | 106 | it_should_behave_like 'a generated SQL SELECT query' 107 | 108 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM "users" ORDER BY "id", "name", "age"') } 109 | its(:to_subquery) { should eql('(SELECT * FROM "users" ORDER BY "id", "name", "age")') } 110 | end 111 | 112 | context 'when the operand is limited' do 113 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] }.take(1) } 114 | 115 | it_should_behave_like 'a generated SQL SELECT query' 116 | 117 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" ORDER BY "id" DESC, "name" DESC, "age" DESC') } 118 | its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" LIMIT 1) AS "users" ORDER BY "id" DESC, "name" DESC, "age" DESC)') } 119 | end 120 | 121 | context 'when the operand is an offset' do 122 | let(:operand) { base_relation.sort_by { |r| [r.id, r.name, r.age] }.drop(1) } 123 | 124 | it_should_behave_like 'a generated SQL SELECT query' 125 | 126 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "users" ORDER BY "id" DESC, "name" DESC, "age" DESC') } 127 | its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" ORDER BY "id", "name", "age" OFFSET 1) AS "users" ORDER BY "id" DESC, "name" DESC, "age" DESC)') } 128 | end 129 | 130 | context 'when the operand is a difference' do 131 | let(:operand) { base_relation.difference(base_relation).sort_by { |r| [r.id, r.name, r.age] } } 132 | 133 | it_should_behave_like 'a generated SQL SELECT query' 134 | 135 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id" DESC, "name" DESC, "age" DESC') } 136 | its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") EXCEPT (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id" DESC, "name" DESC, "age" DESC)') } 137 | end 138 | 139 | context 'when the operand is an intersection' do 140 | let(:operand) { base_relation.intersect(base_relation).sort_by { |r| [r.id, r.name, r.age] } } 141 | 142 | it_should_behave_like 'a generated SQL SELECT query' 143 | 144 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id" DESC, "name" DESC, "age" DESC') } 145 | its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") INTERSECT (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id" DESC, "name" DESC, "age" DESC)') } 146 | end 147 | 148 | context 'when the operand is a union' do 149 | let(:operand) { base_relation.union(base_relation).sort_by { |r| [r.id, r.name, r.age] } } 150 | 151 | it_should_behave_like 'a generated SQL SELECT query' 152 | 153 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id" DESC, "name" DESC, "age" DESC') } 154 | its(:to_subquery) { should eql('(SELECT * FROM ((SELECT "id", "name", "age" FROM "users") UNION (SELECT "id", "name", "age" FROM "users")) AS "users" ORDER BY "id" DESC, "name" DESC, "age" DESC)') } 155 | end 156 | 157 | context 'when the operand is a join' do 158 | let(:operand) { base_relation.join(base_relation).sort_by { |r| [r.id, r.name, r.age] } } 159 | 160 | it_should_behave_like 'a generated SQL SELECT query' 161 | 162 | its(:to_s) { should eql('SELECT "id", "name", "age" FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" ORDER BY "id" DESC, "name" DESC, "age" DESC') } 163 | its(:to_subquery) { should eql('(SELECT * FROM (SELECT * FROM "users" AS "left" NATURAL JOIN "users" AS "right") AS "users" ORDER BY "id" DESC, "name" DESC, "age" DESC)') } 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/visit_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation, '#visit' do 6 | subject { object.visit(visitable) } 7 | 8 | let(:described_class) { Class.new(SQL::Generator::Relation) } 9 | let(:object) { described_class.new } 10 | 11 | context 'with a handled object' do 12 | let(:visitable) { double('Visitable') } 13 | 14 | before do 15 | described_class.class_eval do 16 | def visit_rspec_mocks_mock(mock) 17 | mock 18 | end 19 | end 20 | end 21 | 22 | it_should_behave_like 'a command method' 23 | 24 | specify { expect { subject }.to change(object, :frozen?).from(false).to(true) } 25 | end 26 | 27 | context 'with a handled object more than once' do 28 | let(:visitable) { double('Visitable') } 29 | 30 | before do 31 | described_class.class_eval do 32 | def visit_rspec_mocks_mock(mock) 33 | mock 34 | end 35 | end 36 | end 37 | 38 | before do 39 | object.visit(visitable) 40 | end 41 | 42 | if RUBY_VERSION >= '1.9' 43 | specify { expect { subject }.to raise_error(RuntimeError) } 44 | else 45 | specify { expect { subject }.to raise_error(TypeError) } 46 | end 47 | end 48 | 49 | context 'with an unhandled object' do 50 | let(:visitable) { double('Not Handled') } 51 | 52 | specify { expect { subject }.to raise_error(SQL::Generator::Visitor::UnknownObject, "No handler for #{visitable.class} in #{object.class}") } 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/relation/visited_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Relation, '#visited?' do 6 | subject { object.visited? } 7 | 8 | let(:id) { Attribute::Integer.new(:id) } 9 | let(:name) { Attribute::String.new(:name) } 10 | let(:age) { Attribute::Integer.new(:age, required: false) } 11 | let(:header) { [id, name, age] } 12 | let(:body) { [[1, 'Dan Kubb', 35]].each } 13 | let(:base_relation) { Relation::Base.new('users', header, body) } 14 | let(:object) { described_class.new } 15 | 16 | context 'when name is nil' do 17 | it_should_behave_like 'an idempotent method' 18 | 19 | it { should be(false) } 20 | end 21 | 22 | context 'when name is set' do 23 | let(:name) { 'test' } 24 | 25 | before do 26 | # subclasses set @name, but nothing in this class 27 | # does does so simulate it being set 28 | object.instance_variable_set(:@name, name) 29 | end 30 | 31 | it_should_behave_like 'an idempotent method' 32 | 33 | it { should be(true) } 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/visitor/class_methods/handler_for_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Visitor, '.handler_for' do 6 | subject { object.handler_for(visitable_class) } 7 | 8 | let(:object) { Class.new(SQL::Generator::Visitor) } 9 | 10 | before :all do 11 | module ::MySpec 12 | class Visitable; end 13 | end 14 | end 15 | 16 | after :all do 17 | MySpec.class_eval { remove_const(:Visitable) } 18 | Object.class_eval { remove_const(:MySpec) } 19 | end 20 | 21 | context 'with an object handled by a public method' do 22 | let(:visitable_class) { MySpec::Visitable } 23 | 24 | before do 25 | object.class_eval do 26 | remove_instance_variable(:@handlers) if instance_variable_defined?(:@handlers) 27 | define_method(:visit_my_spec_visitable) {} 28 | end 29 | end 30 | 31 | after do 32 | object.class_eval do 33 | remove_instance_variable(:@handlers) 34 | remove_method(:visit_my_spec_visitable) 35 | end 36 | end 37 | 38 | it_should_behave_like 'an idempotent method' 39 | 40 | it { should == :visit_my_spec_visitable } 41 | end 42 | 43 | context 'with an object handled by a private method' do 44 | let(:visitable_class) { MySpec::Visitable } 45 | 46 | before do 47 | object.class_eval do 48 | remove_instance_variable(:@handlers) if instance_variable_defined?(:@handlers) 49 | define_method(:visit_my_spec_visitable) {} 50 | private :visit_my_spec_visitable 51 | end 52 | end 53 | 54 | after do 55 | object.class_eval do 56 | remove_instance_variable(:@handlers) 57 | remove_method(:visit_my_spec_visitable) 58 | end 59 | end 60 | 61 | it_should_behave_like 'an idempotent method' 62 | 63 | it { should == :visit_my_spec_visitable } 64 | end 65 | 66 | context 'with an unhandled object' do 67 | let(:visitable_class) { Class.new } 68 | 69 | specify { expect { subject }.to raise_error(object::UnknownObject, "No handler for #{visitable_class} in #{object}") } 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/visitor/visit_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Visitor, '#visit' do 6 | subject { object.visit(visitable) } 7 | 8 | let(:visitable) { double('handled object') } 9 | let(:object) { described_class.new } 10 | 11 | specify { expect { subject }.to raise_error(NotImplementedError, "#{described_class}#visit must be implemented") } 12 | end 13 | -------------------------------------------------------------------------------- /spec/unit/axiom/sql/generator/visitor/visited_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe SQL::Generator::Visitor, '#visited?' do 6 | subject { object.visited? } 7 | 8 | let(:object) { described_class.new } 9 | 10 | specify { expect { subject }.to raise_error(NotImplementedError, "#{described_class}#visited? must be implemented") } 11 | end 12 | -------------------------------------------------------------------------------- /spec/unit/date/iso8601_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe Date, '#iso8601' do 6 | subject { object.iso8601 } 7 | 8 | context 'when the date is frozen' do 9 | let(:object) { described_class.new(2010, 12, 31).freeze } 10 | 11 | it { should respond_to(:to_s) } 12 | 13 | it { should == '2010-12-31' } 14 | end 15 | 16 | context 'when the date is not frozen' do 17 | let(:object) { described_class.new(2010, 12, 31) } 18 | 19 | it { should respond_to(:to_s) } 20 | 21 | it { should == '2010-12-31' } 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/unit/date_time/iso8601_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe DateTime, '#iso8601' do 6 | 7 | # ruby 1.9.3 has problems with fractional nanoseconds 8 | def self.it_supports_nanoseconds(message = 'returns the expected date-time', &block) 9 | if RUBY_VERSION >= '1.9.3' 10 | it(message) { pending('Fix rounding error in 1.9.3', &block) } 11 | else 12 | it(message, &block) 13 | end 14 | end 15 | 16 | let(:object) { described_class.new(2010, 12, 31, 23, 59, 59 + nsec_in_seconds) } 17 | let(:nsec_in_seconds) { 1 - Rational(1, 10**9) } 18 | 19 | context 'with no arguments' do 20 | subject { object.iso8601 } 21 | 22 | context 'when the datetime is frozen' do 23 | before do 24 | object.freeze 25 | end 26 | 27 | it { should respond_to(:to_s) } 28 | 29 | it_supports_nanoseconds do 30 | should == '2010-12-31T23:59:59+00:00' 31 | end 32 | end 33 | 34 | context 'when the datetime is not frozen' do 35 | it { should respond_to(:to_s) } 36 | 37 | it { should == '2010-12-31T23:59:59+00:00' } 38 | end 39 | end 40 | 41 | context 'with a time scale of 0' do 42 | subject { object.iso8601(time_scale) } 43 | 44 | let(:time_scale) { 0 } 45 | 46 | context 'when the datetime is frozen' do 47 | before do 48 | object.freeze 49 | end 50 | 51 | it { should respond_to(:to_s) } 52 | 53 | it_supports_nanoseconds do 54 | should == '2010-12-31T23:59:59+00:00' 55 | end 56 | end 57 | 58 | context 'when the datetime is not frozen' do 59 | it { should respond_to(:to_s) } 60 | 61 | it { should == '2010-12-31T23:59:59+00:00' } 62 | end 63 | end 64 | 65 | context 'with a time scale of 1' do 66 | subject { object.iso8601(time_scale) } 67 | 68 | let(:time_scale) { 1 } 69 | 70 | context 'when the datetime is frozen' do 71 | before do 72 | object.freeze 73 | end 74 | 75 | it { should respond_to(:to_s) } 76 | 77 | it_supports_nanoseconds do 78 | should == '2010-12-31T23:59:59.9+00:00' 79 | end 80 | end 81 | 82 | context 'when the datetime is not frozen' do 83 | it { should respond_to(:to_s) } 84 | 85 | it { should == '2010-12-31T23:59:59.9+00:00' } 86 | end 87 | end 88 | 89 | context 'with a time scale of 9' do 90 | subject { object.iso8601(time_scale) } 91 | 92 | let(:time_scale) { 9 } 93 | 94 | context 'when the datetime is frozen' do 95 | before do 96 | object.freeze 97 | end 98 | 99 | it { should respond_to(:to_s) } 100 | 101 | it_supports_nanoseconds do 102 | should == '2010-12-31T23:59:59.999999999+00:00' 103 | end 104 | end 105 | 106 | context 'when the datetime is not frozen' do 107 | it { should respond_to(:to_s) } 108 | 109 | it { should == '2010-12-31T23:59:59.999999999+00:00' } 110 | end 111 | end 112 | end 113 | --------------------------------------------------------------------------------