rasti-db 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/README.md +8 -0
- data/lib/rasti/db.rb +9 -18
- data/lib/rasti/db/collection.rb +1 -1
- data/lib/rasti/db/nql/invalid_expression_error.rb +19 -0
- data/lib/rasti/db/nql/nodes/binary_node.rb +25 -0
- data/lib/rasti/db/nql/nodes/comparisons/base.rb +17 -0
- data/lib/rasti/db/nql/nodes/comparisons/equal.rb +17 -0
- data/lib/rasti/db/nql/nodes/comparisons/greater_than.rb +17 -0
- data/lib/rasti/db/nql/nodes/comparisons/greater_than_or_equal.rb +17 -0
- data/lib/rasti/db/nql/nodes/comparisons/include.rb +17 -0
- data/lib/rasti/db/nql/nodes/comparisons/less_than.rb +17 -0
- data/lib/rasti/db/nql/nodes/comparisons/less_than_or_equal.rb +17 -0
- data/lib/rasti/db/nql/nodes/comparisons/like.rb +17 -0
- data/lib/rasti/db/nql/nodes/comparisons/not_equal.rb +17 -0
- data/lib/rasti/db/nql/nodes/comparisons/not_include.rb +17 -0
- data/lib/rasti/db/nql/nodes/conjunction.rb +15 -0
- data/lib/rasti/db/nql/nodes/constants/false.rb +17 -0
- data/lib/rasti/db/nql/nodes/constants/float.rb +17 -0
- data/lib/rasti/db/nql/nodes/constants/integer.rb +17 -0
- data/lib/rasti/db/nql/nodes/constants/literal_string.rb +17 -0
- data/lib/rasti/db/nql/nodes/constants/string.rb +17 -0
- data/lib/rasti/db/nql/nodes/constants/time.rb +23 -0
- data/lib/rasti/db/nql/nodes/constants/true.rb +17 -0
- data/lib/rasti/db/nql/nodes/disjunction.rb +15 -0
- data/lib/rasti/db/nql/nodes/field.rb +23 -0
- data/lib/rasti/db/nql/nodes/parenthesis_sentence.rb +19 -0
- data/lib/rasti/db/nql/nodes/sentence.rb +19 -0
- data/lib/rasti/db/nql/syntax.rb +2266 -0
- data/lib/rasti/db/nql/syntax.treetop +168 -0
- data/lib/rasti/db/query.rb +15 -0
- data/lib/rasti/db/relations/graph_builder.rb +3 -3
- data/lib/rasti/db/version.rb +1 -1
- data/rasti-db.gemspec +3 -1
- data/spec/coverage_helper.rb +5 -1
- data/spec/nql/dependency_tables_spec.rb +35 -0
- data/spec/nql/filter_condition_spec.rb +146 -0
- data/spec/nql/syntax_parser_spec.rb +209 -0
- data/spec/query_spec.rb +53 -0
- metadata +68 -2
| @@ -0,0 +1,168 @@ | |
| 1 | 
            +
            module Rasti
         | 
| 2 | 
            +
              module DB
         | 
| 3 | 
            +
                module NQL
         | 
| 4 | 
            +
                  grammar Syntax
         | 
| 5 | 
            +
                    
         | 
| 6 | 
            +
                    rule sentence
         | 
| 7 | 
            +
                      space* proposition:proposition space* <Nodes::Sentence>
         | 
| 8 | 
            +
                    end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                    rule proposition
         | 
| 11 | 
            +
                      disjunction /
         | 
| 12 | 
            +
                      conjunction /
         | 
| 13 | 
            +
                      statement
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    rule disjunction
         | 
| 17 | 
            +
                      left:(conjunction / statement) space* '|' space* right:proposition <Nodes::Disjunction>
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    rule conjunction
         | 
| 21 | 
            +
                      left:statement space* '&' space* right:(conjunction / statement) <Nodes::Conjunction>
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    rule statement
         | 
| 25 | 
            +
                      parenthesis_sentence /
         | 
| 26 | 
            +
                      comparison
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    rule parenthesis_sentence
         | 
| 30 | 
            +
                      '(' sentence ')' <Nodes::ParenthesisSentence>
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    rule comparison
         | 
| 34 | 
            +
                      comparison_not_include           /
         | 
| 35 | 
            +
                      comparison_include               /
         | 
| 36 | 
            +
                      comparison_like                  /
         | 
| 37 | 
            +
                      comparison_greater_than_or_equal /
         | 
| 38 | 
            +
                      comparison_less_than_or_equal    /
         | 
| 39 | 
            +
                      comparison_greater_than          /
         | 
| 40 | 
            +
                      comparison_less_than             /
         | 
| 41 | 
            +
                      comparison_not_equal             /
         | 
| 42 | 
            +
                      comparison_equal
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    rule field
         | 
| 46 | 
            +
                      _tables:(table:field_name '.')* _column:field_name <Nodes::Field>
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    rule comparison_include
         | 
| 50 | 
            +
                      field:field space* comparator:':' space* argument:basic <Nodes::Comparisons::Include>
         | 
| 51 | 
            +
                    end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    rule comparison_not_include
         | 
| 54 | 
            +
                      field:field space* comparator:'!:' space* argument:basic <Nodes::Comparisons::NotInclude>
         | 
| 55 | 
            +
                    end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                    rule comparison_like
         | 
| 58 | 
            +
                      field:field space* comparator:'~' space* argument:basic <Nodes::Comparisons::Like>
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    rule comparison_greater_than
         | 
| 62 | 
            +
                      field:field space* comparator:'>' space* argument:basic <Nodes::Comparisons::GreaterThan>
         | 
| 63 | 
            +
                    end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                    rule comparison_greater_than_or_equal
         | 
| 66 | 
            +
                      field:field space* comparator:'>=' space* argument:basic <Nodes::Comparisons::GreaterThanOrEqual>
         | 
| 67 | 
            +
                    end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                    rule comparison_less_than
         | 
| 70 | 
            +
                      field:field space* comparator:'<' space* argument:basic <Nodes::Comparisons::LessThan>
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    rule comparison_less_than_or_equal
         | 
| 74 | 
            +
                      field:field space* comparator:'<=' space* argument:basic <Nodes::Comparisons::LessThanOrEqual>
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                    rule comparison_not_equal
         | 
| 78 | 
            +
                      field:field space* comparator:'!=' space* argument:basic <Nodes::Comparisons::NotEqual>
         | 
| 79 | 
            +
                    end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    rule comparison_equal
         | 
| 82 | 
            +
                      field:field space* comparator:'=' space* argument:basic <Nodes::Comparisons::Equal>
         | 
| 83 | 
            +
                    end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                    rule basic
         | 
| 86 | 
            +
                      boolean        /
         | 
| 87 | 
            +
                      time           /
         | 
| 88 | 
            +
                      float          /
         | 
| 89 | 
            +
                      integer        /
         | 
| 90 | 
            +
                      literal_string /
         | 
| 91 | 
            +
                      string
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                    rule space
         | 
| 95 | 
            +
                      [\s\t\n]
         | 
| 96 | 
            +
                    end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                    rule field_name
         | 
| 99 | 
            +
                      [a-z_]+
         | 
| 100 | 
            +
                    end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                    rule time
         | 
| 103 | 
            +
                      date:(value:date 'T')? hour:(digit digit) ':' minutes:(digit digit) seconds:(':' value:(digit digit))? timezone:(value:timezone)? <Nodes::Constants::Time>
         | 
| 104 | 
            +
                    end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                    rule date
         | 
| 107 | 
            +
                      year:(digit digit digit digit) '-' month:(digit digit) '-' day:(digit digit)
         | 
| 108 | 
            +
                    end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                    rule timezone
         | 
| 111 | 
            +
                      sign:sign hour:(digit digit) ':' minutes:(digit digit)
         | 
| 112 | 
            +
                    end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                    rule sign
         | 
| 115 | 
            +
                      '+' / 
         | 
| 116 | 
            +
                      '-'
         | 
| 117 | 
            +
                    end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                    rule literal_string
         | 
| 120 | 
            +
                      '"' string:any_character+ '"' <Nodes::Constants::LiteralString>
         | 
| 121 | 
            +
                    end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                    rule string
         | 
| 124 | 
            +
                      valid_character+ <Nodes::Constants::String>
         | 
| 125 | 
            +
                    end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                    rule any_character
         | 
| 128 | 
            +
                      valid_character /
         | 
| 129 | 
            +
                      reserved_character
         | 
| 130 | 
            +
                    end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                    rule valid_character 
         | 
| 133 | 
            +
                      [0-9a-zA-ZÁÀÄÂÃÅĀĂǍáàäâãåāăǎÉÈËÊĒĔĖĚéèëêēĕėěÍÌÏÎĨĬǏíìïîĩĭǐÓÒÖÔÕŌŎŐǑóòöôõōŏőǒÚÙÜÛŨŪŬŮŰǓúùüûũūŭůűǔÑñçÇ%@#+-_'?!$*/\s]
         | 
| 134 | 
            +
                    end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                    rule boolean
         | 
| 137 | 
            +
                      true /
         | 
| 138 | 
            +
                      false
         | 
| 139 | 
            +
                    end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                    rule true
         | 
| 142 | 
            +
                      'true' <Nodes::Constants::True>
         | 
| 143 | 
            +
                    end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                    rule false
         | 
| 146 | 
            +
                      'false' <Nodes::Constants::False>
         | 
| 147 | 
            +
                    end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                    rule float
         | 
| 150 | 
            +
                      digit+ '.' digit+ <Nodes::Constants::Float>
         | 
| 151 | 
            +
                    end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                    rule integer
         | 
| 154 | 
            +
                      digit+ <Nodes::Constants::Integer>
         | 
| 155 | 
            +
                    end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                    rule digit
         | 
| 158 | 
            +
                      [0-9]
         | 
| 159 | 
            +
                    end
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                    rule reserved_character
         | 
| 162 | 
            +
                      [&|.():!=<>~]
         | 
| 163 | 
            +
                    end
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                  end
         | 
| 166 | 
            +
                end
         | 
| 167 | 
            +
              end
         | 
| 168 | 
            +
            end
         | 
    
        data/lib/rasti/db/query.rb
    CHANGED
    
    | @@ -92,6 +92,17 @@ module Rasti | |
| 92 92 | 
             
                  end
         | 
| 93 93 | 
             
                  alias_method :inspect, :to_s
         | 
| 94 94 |  | 
| 95 | 
            +
                  def nql(filter_expression)
         | 
| 96 | 
            +
                    sentence = nql_parser.parse filter_expression
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                    raise NQL::InvalidExpressionError.new(filter_expression) if sentence.nil?
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                    dependency_tables = sentence.dependency_tables
         | 
| 101 | 
            +
                    query = dependency_tables.empty? ? self : join(*dependency_tables)
         | 
| 102 | 
            +
                    
         | 
| 103 | 
            +
                    query.where sentence.filter_condition
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
             | 
| 95 106 | 
             
                  private
         | 
| 96 107 |  | 
| 97 108 | 
             
                  def chainable(&block)
         | 
| @@ -122,6 +133,10 @@ module Rasti | |
| 122 133 | 
             
                    collection_class.queries.key?(method) || super
         | 
| 123 134 | 
             
                  end
         | 
| 124 135 |  | 
| 136 | 
            +
                  def nql_parser
         | 
| 137 | 
            +
                    NQL::SyntaxParser.new
         | 
| 138 | 
            +
                  end
         | 
| 139 | 
            +
             | 
| 125 140 | 
             
                  attr_reader :collection_class, :dataset, :relations, :schema
         | 
| 126 141 |  | 
| 127 142 | 
             
                end
         | 
| @@ -14,7 +14,7 @@ module Rasti | |
| 14 14 | 
             
                      end
         | 
| 15 15 |  | 
| 16 16 | 
             
                      def joins_to(dataset, relations, collection_class, schema=nil)
         | 
| 17 | 
            -
                        ds =  | 
| 17 | 
            +
                        ds = recursive_joins dataset, recursive_parse(relations), collection_class, schema
         | 
| 18 18 | 
             
                        qualified_collection_name = schema ? Sequel[schema][collection_class.collection_name] : Sequel[collection_class.collection_name]
         | 
| 19 19 | 
             
                        ds.distinct.select_all(qualified_collection_name)
         | 
| 20 20 | 
             
                      end
         | 
| @@ -41,13 +41,13 @@ module Rasti | |
| 41 41 | 
             
                        end
         | 
| 42 42 | 
             
                      end
         | 
| 43 43 |  | 
| 44 | 
            -
                      def  | 
| 44 | 
            +
                      def recursive_joins(dataset, joins, collection_class, schema, prefix=nil)
         | 
| 45 45 | 
             
                        joins.each do |relation_name, nested_joins|
         | 
| 46 46 | 
             
                          relation = get_relation collection_class, relation_name
         | 
| 47 47 |  | 
| 48 48 | 
             
                          dataset = relation.join_to dataset, schema, prefix
         | 
| 49 49 |  | 
| 50 | 
            -
                          dataset =  | 
| 50 | 
            +
                          dataset = recursive_joins dataset, nested_joins, relation.target_collection_class, schema, relation.join_relation_name(prefix) unless nested_joins.empty?
         | 
| 51 51 | 
             
                        end
         | 
| 52 52 |  | 
| 53 53 | 
             
                        dataset
         | 
    
        data/lib/rasti/db/version.rb
    CHANGED
    
    
    
        data/rasti-db.gemspec
    CHANGED
    
    | @@ -19,13 +19,15 @@ Gem::Specification.new do |spec| | |
| 19 19 | 
             
              spec.require_paths = ['lib']
         | 
| 20 20 |  | 
| 21 21 | 
             
              spec.add_runtime_dependency 'sequel', '~> 5.0'
         | 
| 22 | 
            +
              spec.add_runtime_dependency 'treetop', '~> 1.4.8'
         | 
| 22 23 | 
             
              spec.add_runtime_dependency 'consty', '~> 1.0', '>= 1.0.3'
         | 
| 23 24 | 
             
              spec.add_runtime_dependency 'timing', '~> 0.1', '>= 0.1.3'
         | 
| 24 25 | 
             
              spec.add_runtime_dependency 'class_config', '~> 0.0', '>= 0.0.2'
         | 
| 26 | 
            +
              spec.add_runtime_dependency 'multi_require', '~> 1.0'
         | 
| 25 27 |  | 
| 26 28 | 
             
              spec.add_development_dependency 'bundler', '~> 1.12'
         | 
| 27 29 | 
             
              spec.add_development_dependency 'rake', '~> 11.0'
         | 
| 28 | 
            -
              spec.add_development_dependency 'minitest', '~> 5.0'
         | 
| 30 | 
            +
              spec.add_development_dependency 'minitest', '~> 5.0', '< 5.11'
         | 
| 29 31 | 
             
              spec.add_development_dependency 'minitest-colorin', '~> 0.1'
         | 
| 30 32 | 
             
              spec.add_development_dependency 'minitest-line', '~> 0.6'
         | 
| 31 33 | 
             
              spec.add_development_dependency 'simplecov', '~> 0.12'
         | 
    
        data/spec/coverage_helper.rb
    CHANGED
    
    | @@ -2,4 +2,8 @@ require 'simplecov' | |
| 2 2 | 
             
            require 'coveralls'
         | 
| 3 3 |  | 
| 4 4 | 
             
            SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new [SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter]
         | 
| 5 | 
            -
             | 
| 5 | 
            +
             | 
| 6 | 
            +
            SimpleCov.start do
         | 
| 7 | 
            +
              add_filter 'lib/rasti/db/nql/syntax.rb'
         | 
| 8 | 
            +
              add_filter 'spec/'
         | 
| 9 | 
            +
            end
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            require 'minitest_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe 'NQL::DepedencyTables' do
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              let(:parser) { Rasti::DB::NQL::SyntaxParser.new }
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              def parse(expression)
         | 
| 8 | 
            +
                parser.parse expression
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              it 'must have no dependency tables' do
         | 
| 12 | 
            +
                tree = parse 'column = 1'
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                tree.dependency_tables.must_be_empty
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              it 'must have one dependency table' do
         | 
| 18 | 
            +
                tree = parse 'relation_table_one.relation_table_two.column = 1'
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                tree.dependency_tables.must_equal ['relation_table_one.relation_table_two']
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              it 'must have multiple dependency tables' do
         | 
| 24 | 
            +
                tree = parse 'a.b.c = 1 & (d.e: 2 | f.g.h = 1) | i = 4'
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                tree.dependency_tables.must_equal ['a.b', 'd', 'f.g']
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              it 'must have repeated sub-dependency' do
         | 
| 30 | 
            +
                tree = parse 'a.b.c = 1 & a.d: 2'
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                tree.dependency_tables.must_equal ['a.b', 'a']
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            end
         | 
| @@ -0,0 +1,146 @@ | |
| 1 | 
            +
            require 'minitest_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe 'NQL::FilterCondition' do
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              let(:parser) { Rasti::DB::NQL::SyntaxParser.new }
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              def filter_condition(expression)
         | 
| 8 | 
            +
                tree = parser.parse expression
         | 
| 9 | 
            +
                tree.filter_condition
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def assert_identifier(identifier, expected_value)
         | 
| 13 | 
            +
                identifier.must_be_instance_of Sequel::SQL::Identifier
         | 
| 14 | 
            +
                identifier.value.must_equal expected_value
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def assert_comparison(filter, expected_left, expected_comparator, expected_right)
         | 
| 18 | 
            +
                filter.must_be_instance_of Sequel::SQL::BooleanExpression
         | 
| 19 | 
            +
                filter.op.must_equal expected_comparator.to_sym
         | 
| 20 | 
            +
                
         | 
| 21 | 
            +
                left, right = filter.args
         | 
| 22 | 
            +
                assert_identifier left, expected_left
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                right.must_equal expected_right
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              describe 'Comparison' do
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                it 'must create filter from expression with <' do
         | 
| 30 | 
            +
                  splitted_expression = ['column', '<', 1]
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  assert_comparison filter_condition(splitted_expression.join(' ')), *splitted_expression
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                it 'must create filter from expression with >' do
         | 
| 36 | 
            +
                  splitted_expression = ['column', '>', 1]
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  assert_comparison filter_condition(splitted_expression.join(' ')), *splitted_expression
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                it 'must create filter from expression with <=' do
         | 
| 42 | 
            +
                  splitted_expression = ['column', '<=', 1]
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  assert_comparison filter_condition(splitted_expression.join(' ')), *splitted_expression
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                it 'must create filter from expression with >=' do
         | 
| 48 | 
            +
                  splitted_expression = ['column', '>=', 1]
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  assert_comparison filter_condition(splitted_expression.join(' ')), *splitted_expression
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                it 'must create filter from expression with !=' do
         | 
| 54 | 
            +
                  splitted_expression = ['column', '!=', 1]
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  assert_comparison filter_condition(splitted_expression.join(' ')), *splitted_expression
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                it 'must create filter from expression with =' do
         | 
| 60 | 
            +
                  filter = filter_condition 'column = 1'
         | 
| 61 | 
            +
                  identifier, value = filter.first
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  assert_identifier identifier, 'column'
         | 
| 64 | 
            +
                  value.must_equal 1
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                it 'must create filter from expression with ~' do
         | 
| 68 | 
            +
                  filter = filter_condition 'column ~ test'
         | 
| 69 | 
            +
                  assert_comparison filter, 'column', 'ILIKE', 'test'
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                it 'must create filter from expression with :' do
         | 
| 73 | 
            +
                  filter = filter_condition 'column: test'
         | 
| 74 | 
            +
                  assert_comparison filter, 'column', 'ILIKE', '%test%'
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                it 'must create filter from expression with !:' do
         | 
| 78 | 
            +
                  filter = filter_condition 'column!: test'
         | 
| 79 | 
            +
                  assert_comparison filter, 'column', 'NOT ILIKE', '%test%'
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
              describe 'Constants' do
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                it 'must create filter from expression with LiteralString' do
         | 
| 87 | 
            +
                  filter = filter_condition 'column: "test "'
         | 
| 88 | 
            +
                  assert_comparison filter, 'column', 'ILIKE', '%test %'
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                it 'must create filter from expression with Time' do
         | 
| 92 | 
            +
                  filter = filter_condition 'column > 2019-03-27T12:20:00-03:00'
         | 
| 93 | 
            +
                  assert_comparison filter, 'column', '>', '2019-03-27 12:20:00 -0300'
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
              end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
              it 'must create filter from expression with field with multiple tables' do
         | 
| 99 | 
            +
                filter = filter_condition 'table_one.table_two.column = test'
         | 
| 100 | 
            +
                identifier, value = filter.first
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                identifier.must_be_instance_of Sequel::SQL::QualifiedIdentifier
         | 
| 103 | 
            +
                identifier.table.must_equal 'table_one__table_two'
         | 
| 104 | 
            +
                identifier.column.must_equal :column
         | 
| 105 | 
            +
                value.must_equal 'test'
         | 
| 106 | 
            +
              end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
              it 'must create filter from expression with conjunction' do
         | 
| 109 | 
            +
                filter = filter_condition 'column_one > 1 & column_two < 3'
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                filter.must_be_instance_of Sequel::SQL::BooleanExpression
         | 
| 112 | 
            +
                filter.op.must_equal :AND
         | 
| 113 | 
            +
                major_expression, minor_expression = filter.args
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                assert_comparison major_expression, 'column_one', '>', 1
         | 
| 116 | 
            +
                assert_comparison minor_expression, 'column_two', '<', 3
         | 
| 117 | 
            +
              end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
              it 'must create filter from expression with disjunction' do
         | 
| 120 | 
            +
                filter = filter_condition 'column_one > 1 | column_two != 3'
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                filter.must_be_instance_of Sequel::SQL::BooleanExpression
         | 
| 123 | 
            +
                filter.op.must_equal :OR
         | 
| 124 | 
            +
                major_expression, not_equal_expression = filter.args
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                assert_comparison major_expression, 'column_one', '>', 1
         | 
| 127 | 
            +
                assert_comparison not_equal_expression, 'column_two', '!=', 3
         | 
| 128 | 
            +
              end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
              it 'must create filter from expression with parenthesis' do
         | 
| 131 | 
            +
                filter = filter_condition 'column_one > 1 & (column_two != 3 | column_three < 5)'
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                filter.must_be_instance_of Sequel::SQL::BooleanExpression
         | 
| 134 | 
            +
                filter.op.must_equal :AND
         | 
| 135 | 
            +
                
         | 
| 136 | 
            +
                major_expression, and_expression = filter.args
         | 
| 137 | 
            +
                assert_comparison major_expression, 'column_one', '>', 1
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                and_expression.op.must_equal :OR
         | 
| 140 | 
            +
                not_equal_expression, minor_expression = and_expression.args
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                assert_comparison not_equal_expression, 'column_two', '!=', 3
         | 
| 143 | 
            +
                assert_comparison minor_expression, 'column_three', '<', 5
         | 
| 144 | 
            +
              end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
            end
         | 
| @@ -0,0 +1,209 @@ | |
| 1 | 
            +
            require 'minitest_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe 'NQL::SyntaxParser' do
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              let(:parser) { Rasti::DB::NQL::SyntaxParser.new }
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              def parse(expression)
         | 
| 8 | 
            +
                parser.parse(expression).tap do |tree|
         | 
| 9 | 
            +
                  tree.wont_be_nil
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              describe 'Comparison' do
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                describe 'Comparators' do
         | 
| 16 | 
            +
                  
         | 
| 17 | 
            +
                  {
         | 
| 18 | 
            +
                    '='  => Rasti::DB::NQL::Nodes::Comparisons::Equal,
         | 
| 19 | 
            +
                    '!=' => Rasti::DB::NQL::Nodes::Comparisons::NotEqual,
         | 
| 20 | 
            +
                    '>'  => Rasti::DB::NQL::Nodes::Comparisons::GreaterThan,
         | 
| 21 | 
            +
                    '>=' => Rasti::DB::NQL::Nodes::Comparisons::GreaterThanOrEqual,
         | 
| 22 | 
            +
                    '<'  => Rasti::DB::NQL::Nodes::Comparisons::LessThan,
         | 
| 23 | 
            +
                    '<=' => Rasti::DB::NQL::Nodes::Comparisons::LessThanOrEqual,
         | 
| 24 | 
            +
                    '~'  => Rasti::DB::NQL::Nodes::Comparisons::Like,
         | 
| 25 | 
            +
                    ':'  => Rasti::DB::NQL::Nodes::Comparisons::Include,
         | 
| 26 | 
            +
                    '!:' => Rasti::DB::NQL::Nodes::Comparisons::NotInclude
         | 
| 27 | 
            +
                  }.each do |comparator, node_class|
         | 
| 28 | 
            +
                    it "must parse expression with '#{comparator}'" do
         | 
| 29 | 
            +
                      tree = parse "column #{comparator} value"
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                      proposition = tree.proposition
         | 
| 32 | 
            +
                      proposition.must_be_instance_of node_class
         | 
| 33 | 
            +
                      proposition.comparator.text_value.must_equal comparator
         | 
| 34 | 
            +
                      proposition.field.text_value.must_equal 'column'
         | 
| 35 | 
            +
                      proposition.argument.text_value.must_equal 'value'
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                it "must parse expression without spaces between elements" do
         | 
| 42 | 
            +
                  tree = parse 'column=value'
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  proposition = tree.proposition
         | 
| 45 | 
            +
                  proposition.must_be_instance_of Rasti::DB::NQL::Nodes::Comparisons::Equal
         | 
| 46 | 
            +
                  proposition.comparator.text_value.must_equal '='
         | 
| 47 | 
            +
                  proposition.field.text_value.must_equal 'column'
         | 
| 48 | 
            +
                  proposition.argument.text_value.must_equal 'value'
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                describe 'Right hand Operand' do
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  it 'must parse expression with integer' do
         | 
| 54 | 
            +
                    tree = parse 'column = 1'
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                    right_hand_operand = tree.proposition.argument
         | 
| 57 | 
            +
                    right_hand_operand.must_be_instance_of Rasti::DB::NQL::Nodes::Constants::Integer
         | 
| 58 | 
            +
                    right_hand_operand.value.must_equal 1
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  it 'must parse expression with float' do
         | 
| 62 | 
            +
                    tree = parse 'column = 2.3'
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                    right_hand_operand = tree.proposition.argument
         | 
| 65 | 
            +
                    right_hand_operand.must_be_instance_of Rasti::DB::NQL::Nodes::Constants::Float
         | 
| 66 | 
            +
                    right_hand_operand.value.must_equal 2.3
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  it 'must parse expression with true' do
         | 
| 70 | 
            +
                    tree = parse 'column = true'
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                    right_hand_operand = tree.proposition.argument
         | 
| 73 | 
            +
                    right_hand_operand.must_be_instance_of Rasti::DB::NQL::Nodes::Constants::True
         | 
| 74 | 
            +
                    right_hand_operand.value.must_equal true
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                  it 'must parse expression with false' do
         | 
| 78 | 
            +
                    tree = parse 'column = false'
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                    right_hand_operand = tree.proposition.argument
         | 
| 81 | 
            +
                    right_hand_operand.must_be_instance_of Rasti::DB::NQL::Nodes::Constants::False
         | 
| 82 | 
            +
                    right_hand_operand.value.must_equal false
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  it 'must parse expression with string' do
         | 
| 86 | 
            +
                    tree = parse 'column = String1 '
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                    right_hand_operand = tree.proposition.argument
         | 
| 89 | 
            +
                    right_hand_operand.must_be_instance_of Rasti::DB::NQL::Nodes::Constants::String
         | 
| 90 | 
            +
                    right_hand_operand.value.must_equal 'String1'
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  it 'must parse expression with literal string' do
         | 
| 94 | 
            +
                    tree = parse 'column = "a & (b | c) | d:=e"'
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                    right_hand_operand = tree.proposition.argument
         | 
| 97 | 
            +
                    right_hand_operand.must_be_instance_of Rasti::DB::NQL::Nodes::Constants::LiteralString
         | 
| 98 | 
            +
                    right_hand_operand.value.must_equal 'a & (b | c) | d:=e'
         | 
| 99 | 
            +
                  end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                  describe 'Time' do
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                    it 'must parse expression with hours and minutes' do
         | 
| 104 | 
            +
                      tree = parse 'column > 12:20'
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                      right_hand_operand = tree.proposition.argument
         | 
| 107 | 
            +
                      right_hand_operand.must_be_instance_of Rasti::DB::NQL::Nodes::Constants::Time
         | 
| 108 | 
            +
                      right_hand_operand.value.must_equal Timing::TimeInZone.parse('12:20').to_s
         | 
| 109 | 
            +
                    end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    it 'must parse expression with date, hours, minutes and seconds' do
         | 
| 112 | 
            +
                      tree = parse 'column > 2019-03-27T12:20:00'
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                      right_hand_operand = tree.proposition.argument
         | 
| 115 | 
            +
                      right_hand_operand.must_be_instance_of Rasti::DB::NQL::Nodes::Constants::Time
         | 
| 116 | 
            +
                      right_hand_operand.value.must_equal Timing::TimeInZone.parse('2019-03-27T12:20:00').to_s
         | 
| 117 | 
            +
                    end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                    it 'must parse expression with date, hours, minutes, seconds and timezone' do
         | 
| 120 | 
            +
                      tree = parse 'column > 2019-03-27T12:20:00-03:00'
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                      right_hand_operand = tree.proposition.argument
         | 
| 123 | 
            +
                      right_hand_operand.must_be_instance_of Rasti::DB::NQL::Nodes::Constants::Time
         | 
| 124 | 
            +
                      right_hand_operand.value.must_equal Timing::TimeInZone.parse('2019-03-27T12:20:00-03:00').to_s
         | 
| 125 | 
            +
                    end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                  end
         | 
| 128 | 
            +
                
         | 
| 129 | 
            +
                end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                it 'must parse expression with field with tables' do
         | 
| 132 | 
            +
                  tree = parse 'relation_table_one.relation_table_two.column = 1'
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                  left_hand_operand = tree.proposition.field
         | 
| 135 | 
            +
                  left_hand_operand.tables.must_equal ['relation_table_one', 'relation_table_two']
         | 
| 136 | 
            +
                  left_hand_operand.column.must_equal 'column'
         | 
| 137 | 
            +
                end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
              end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
              describe 'Parenthesis Sentence' do
         | 
| 142 | 
            +
                
         | 
| 143 | 
            +
                it 'must parse parenthesis comparison' do
         | 
| 144 | 
            +
                  tree = parse '(column: name)'
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                  tree.proposition.sentence.text_value.must_equal 'column: name'
         | 
| 147 | 
            +
                end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                it 'must parse conjunction over parenthesis of disjunction' do
         | 
| 150 | 
            +
                  tree = parse '(column_one: 1 | column_two: two) & column_three: 3.0'
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                  tree.proposition.must_be_instance_of Rasti::DB::NQL::Nodes::Conjunction
         | 
| 153 | 
            +
                  tree.proposition.left.sentence.text_value.must_equal 'column_one: 1 | column_two: two'
         | 
| 154 | 
            +
                end
         | 
| 155 | 
            +
             | 
| 156 | 
            +
              end
         | 
| 157 | 
            +
             | 
| 158 | 
            +
             | 
| 159 | 
            +
              describe 'Conjunction' do
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                def parse_and_assert(expression, conjunction_values)
         | 
| 162 | 
            +
                  tree = parse expression
         | 
| 163 | 
            +
                  
         | 
| 164 | 
            +
                  tree.proposition.must_be_instance_of Rasti::DB::NQL::Nodes::Conjunction
         | 
| 165 | 
            +
                  tree.proposition.values.map(&:text_value).zip(conjunction_values).each do |actual, expected|
         | 
| 166 | 
            +
                    actual.must_equal expected
         | 
| 167 | 
            +
                  end
         | 
| 168 | 
            +
                end
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                it 'must parse conjunction of two comparisons' do
         | 
| 171 | 
            +
                  parse_and_assert 'column_one != 1 & column_two: name', ['column_one != 1', 'column_two: name']
         | 
| 172 | 
            +
                end
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                it 'must parse multiple conjunctions' do
         | 
| 175 | 
            +
                  parse_and_assert 'column_one > 1 & column_two: name & column_three < 9.2', ['column_one > 1', 'column_two: name ', 'column_three < 9.2']
         | 
| 176 | 
            +
                end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                it 'must parse conjunction with parenthesis expression' do
         | 
| 179 | 
            +
                  parse_and_assert '(column_one > 1 | column_two: name) & column_three < 9.2', ['(column_one > 1 | column_two: name)', 'column_three < 9.2']
         | 
| 180 | 
            +
                end
         | 
| 181 | 
            +
             | 
| 182 | 
            +
              end
         | 
| 183 | 
            +
             | 
| 184 | 
            +
              describe 'Disjunction' do
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                def parse_and_assert(expression, disjunction_values)
         | 
| 187 | 
            +
                  tree = parse expression
         | 
| 188 | 
            +
                  
         | 
| 189 | 
            +
                  tree.proposition.must_be_instance_of Rasti::DB::NQL::Nodes::Disjunction
         | 
| 190 | 
            +
                  tree.proposition.values.map(&:text_value).zip(disjunction_values).each do |actual, expected|
         | 
| 191 | 
            +
                    actual.must_equal expected
         | 
| 192 | 
            +
                  end
         | 
| 193 | 
            +
                end
         | 
| 194 | 
            +
             | 
| 195 | 
            +
                it 'must parse disjunction of two disjunctions' do
         | 
| 196 | 
            +
                  parse_and_assert 'column_one != 1 | column_two: name', ['column_one != 1', 'column_two: name'] 
         | 
| 197 | 
            +
                end
         | 
| 198 | 
            +
             | 
| 199 | 
            +
                it 'must parse disjunction over conjunction' do
         | 
| 200 | 
            +
                  parse_and_assert 'column_one != 1 & column_two: name | column_three < 1.2', ['column_one != 1 & column_two: name ', 'column_three < 1.2'] 
         | 
| 201 | 
            +
                end
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                it 'must parse multiple conjunctions' do
         | 
| 204 | 
            +
                  parse_and_assert 'column_one != 1 | column_two: name | column_three < 1.2', ['column_one != 1', 'column_two: name ', 'column_three < 1.2']
         | 
| 205 | 
            +
                end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
              end
         | 
| 208 | 
            +
             | 
| 209 | 
            +
            end
         |