From db5bbcb93aba5b078482dca22f41be203d7407cb Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Tue, 27 Sep 2011 16:35:49 +0200 Subject: [PATCH 01/37] AR-through-reflections in query_builder; SmqlToAR.models returns the reflections-graph; limit, offset, sub-query "()" (not sub-conditions). unstable 0.3 --- .gitignore | 1 + Rakefile | 1 + VERSION | 2 +- lib/smql_to_ar.rb | 48 +++++++++++++-- lib/smql_to_ar/condition_types.rb | 99 ++++++++++++++++++++++++------- lib/smql_to_ar/query_builder.rb | 79 +++++++++++++++++------- 6 files changed, 180 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index 73633ee..1b861d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ pkg/** +.*.swp smql.gemspec diff --git a/Rakefile b/Rakefile index 4245ce4..c9ac2a2 100644 --- a/Rakefile +++ b/Rakefile @@ -15,6 +15,7 @@ begin gem.add_dependency 'activerecord' gem.add_dependency 'activesupport' gem.add_dependency 'json' + gem.add_dependency 'methodphitamine' end Jeweler::GemcutterTasks.new rescue LoadError diff --git a/VERSION b/VERSION index 4e379d2..bcab45a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.2 +0.0.3 diff --git a/lib/smql_to_ar.rb b/lib/smql_to_ar.rb index 63e1a10..9e53dcf 100644 --- a/lib/smql_to_ar.rb +++ b/lib/smql_to_ar.rb @@ -15,6 +15,18 @@ # along with this program. If not, see . class SmqlToAR + module Assertion + def raise_unless cond, exception = nil, *args + cond, exception, *args = yield. cond, exception, *args if block_given? + raise exception || Exception, *args unless cond + end + + def raise_if cond, exception = nil, *args + cond, exception, *args = yield. cond, exception, *args if block_given? + raise exception || Exception, *args if cond + end + end + include ActiveSupport::Benchmarkable ############################################################################r # Exceptions @@ -77,12 +89,18 @@ class SmqlToAR end end - class OnlyOrderOnBaseError < SMQLError + class RootOnlyFunctionError < SMQLError def initialize path super :path => path end end + class ConColumnError < SMQLError + def initialize expected, got + super :expected => expected, :got => got + end + end + class BuilderError < Exception; end ############################################################################# @@ -103,7 +121,7 @@ class SmqlToAR def initialize model, *col @model = model @last_model = nil - *@path, @col = Array.wrap( col).collect {|s| s.to_s.split /[.\/]/ }.flatten.collect &:to_sym + *@path, @col = Array.wrap( col).collect( &it.to_s.split( /[.\/]/)).flatten.collect( &:to_sym) end def last_model @@ -146,6 +164,8 @@ class SmqlToAR exe.call pp, model end end + def length() @path.length+1 end + def size() @path.size+1 end def to_a() @path+[@col] end def to_s() to_a.join '.' end def to_sym() to_s.to_sym end @@ -153,6 +173,7 @@ class SmqlToAR def inspect() "#" end def relation() SmqlToAR.model_of last_model, @col end def allowed?() ! self.protected? end + def child?() @path.empty? and !!relation end end attr_reader :model, :query, :conditions, :builder, :order @@ -179,6 +200,23 @@ class SmqlToAR #p model: @model, query: @query end + def self.models models + models = Array.wrap models + r = Hash.new {|h,k| h[k] = {} } + while model = models.tap( &:uniq!).pop + refls = model.respond_to?( :reflections) && model.reflections + refls && refls.each do |name, refl| + r[model.name][name] = case refl + when ActiveRecord::Reflection::ThroughReflection then {:macro => refl.macro, :model => refl.klass.name, :through => refl.through_reflection.name} + when ActiveRecord::Reflection::AssociationReflection then {:macro => refl.macro, :model => refl.klass.name} + else raise "Ups: #{refl.class}" + end + models.push refl.klass unless r.keys.include? refl.klass.name + end + end + r + end + def parse benchmark 'SMQL parse' do @conditions = ConditionTypes.try_parse @model, @query @@ -187,11 +225,11 @@ class SmqlToAR self end - def build + def build prefix = nil, base_table = nil benchmark 'SMQL build query' do - @builder = QueryBuilder.new @model + @builder = QueryBuilder.new @model, prefix, base_table table = @builder.base_table - @conditions.each {|condition| condition.build builder, table } + @conditions.each &it.build( builder, table) end #p builder: @builder self diff --git a/lib/smql_to_ar/condition_types.rb b/lib/smql_to_ar/condition_types.rb index 23ec136..6f831b7 100644 --- a/lib/smql_to_ar/condition_types.rb +++ b/lib/smql_to_ar/condition_types.rb @@ -25,6 +25,8 @@ class SmqlToAR # Nimmt eine Klasse ein Objekt an, so soll diese Klasse instanziert werden. # Alles weitere siehe Condition. module ConditionTypes + extend SmqlToAR::Assertion + class < [:givenname, :surname, :nick] def split_keys k @@ -40,13 +42,13 @@ class SmqlToAR next if :Condition == c c = const_get c next if Condition === c - raise UnexpectedColOpError.new( model, colop, val) unless colop =~ /^(?:\d*:)?(.*?)(\W*)$/ + raise_unless colop =~ /^(?:\d*:)?(.*?)(\W*)$/, UnexpectedColOpError.new( model, colop, val) col, op = $1, $2 col = split_keys( col).collect {|c| Column.new model, c } r = c.try_parse model, col, op, val break if r end - raise UnexpectedError.new( model, colop, val) unless r + raise_unless r, UnexpectedError.new( model, colop, val) r end @@ -72,6 +74,8 @@ class SmqlToAR end class Condition + include SmqlToAR::Assertion + extend SmqlToAR::Assertion attr_reader :value, :cols Operator = nil Expected = [] @@ -81,7 +85,7 @@ class SmqlToAR # Passt das Object, die Klasse instanzieren. def self.try_parse model, cols, op, val #p :self => name, :try_parse => op, :cols => cols, :with => self::Operator, :value => val, :expected => self::Expected, :model => model.name - new model, cols, val if self::Operator === op and self::Expected.any? {|i| i === val } + new model, cols, val if self::Operator === op and self::Expected.any?( &it === val) end def initialize model, cols, val @@ -103,14 +107,14 @@ class SmqlToAR # Gibt es eine Spalte diesen Namens? # Oder: Gibt es eine Relation diesen Namens? (Hier nicht der Fall) def verify_column col - raise NonExistingColumnError.new( %w[Column], col) unless col.exist_in? + raise_unless col.exist_in?, NonExistingColumnError.new( %w[Column], col) end # Modelle koennen Spalten/Relationen verbieten mit Model#smql_protected. # Dieses muss ein Object mit #include?( name_als_string) zurueckliefern, # welches true fuer verboten und false fuer, erlaubt steht. def verify_allowed col - raise ProtectedColumnError.new( col) if col.protected? + raise_if col.protected?, ProtectedColumnError.new( col) end # Erstelle alle noetigen Klauseln. builder nimmt diese entgegen, @@ -196,31 +200,64 @@ class SmqlToAR super( *pars) cols = {} @cols.each do |col| - col_model = SmqlToAR.model_of col.last_model, col.col - #p col_model: col_model.to_s, value: @value + col_model = col.relation cols[col] = [col_model] + ConditionTypes.try_parse( col_model, @value) end @cols = cols end def verify_column col - refl = SmqlToAR.model_of col.last_model, col.col - #p refl: refl, model: @model.name, col: col, :reflections => @model.reflections.keys - raise NonExistingRelationError.new( %w[Relation], col) unless refl + raise_unless col.relation, NonExistingRelationError.new( %w[Relation], col) end def build builder, table @cols.each do |col, sub| t = table + col.path + [col.col] - #p sub: sub - p col: col, joins: col.joins col.joins.each {|j, m| builder.join table+j, m } - builder.join t, SmqlToAR.model_of( col.last_model, col.col) - sub[1..-1].each {|one| one.build builder, t } + builder.join t, sub[0] + sub[1..-1].each &it.build( builder, t) end self end end + + # Takes to Queries. + # First Query will be a Subquery, second a regular query. + # Example: + # Person.smql 'sub.articles:' => [{'limit:' => 1, 'order:': 'updated_at desc'}, {'content~' => 'some text'}] + # Person must have as last Article (compared by updated_at) owned by Person a Artive which has 'some text' in content. + # The last Article needn't to have 'some text' has content, the subquery takes it anyway. + # But the second query compares to it and never to any other Article, because these are filtered by first query. + # The difference to + # Person.smql :articles => {'content~' => 'some text', 'limit:' => 1, 'order:': 'updated_at desc'} + # is, second is not allowed (limit and order must be in root) and this means something like + # "Person must have the Article owned by Person which has 'some text' in content. + # limit and order has no function in this query and this article needn't to be the last." + class SubEqualJoin < EqualJoin + Operator = '()' + Expected = [lambda {|x| x.kind_of?( Array) and (1..2).include?( x.length) and x.all?( &it.kind_of?( Hash))}] + + def initialize model, cols, val + super model, cols, val[1] + # sub: model, subquery, sub(condition) + @cols.each {|col, sub| sub[ 1...1] = SmqlToAR.new( col.relation, val[0]).parse } + end + + def verify_column col + raise_unless col.child?, ConColumnError.new( [:Column], col) + end + + def build builder, table + @cols.each do |col, sub| + t = table+col.to_a + p t: t, sub: sub + builder.sub_join t, col, *sub[0..1] + sub[2..-1].each &it.build( builder, t) + end + self + end + end + Equal = simple_condition Condition, '=', "%s = %s", [Array, String, Numeric] Equal2 = simple_condition Equal, '', "%s = %s", [String, Numeric] GreaterThan = simple_condition Condition, '>', "%s > %s", [Array, Numeric] @@ -236,7 +273,7 @@ class SmqlToAR Expected = [nil] def verify_column col - raise NonExistingSelectableError.new( col) unless col.exist_in? or SmqlToAR.model_of( col.last_model, col.col) + raise_unless col.exist_in? || SmqlToAR.model_of( col.last_model, col.col), NonExistingSelectableError.new( col) end def build builder, table @@ -258,13 +295,14 @@ class SmqlToAR Expected = [String, Array, Hash, Numeric, nil] class Function + include SmqlToAR::Assertion Name = nil Expected = [] attr_reader :model, :func, :args def self.try_parse model, func, args SmqlToAR.logger.info( { try_parse: [func,args]}.inspect) - self.new model, func, args if self::Name === func and self::Expected.any? {|e| e === args } + self.new model, func, args if self::Name === func and self::Expected.any?( &it === args) end def initialize model, func, args @@ -277,22 +315,19 @@ class SmqlToAR Expected = [String, Array, Hash, nil] def initialize model, func, args - SmqlToAR.logger.info( {args: args}.inspect) args = case args when String then [args] when Array, Hash then args.to_a when nil then nil else raise 'Oops' end - SmqlToAR.logger.info( {args: args}.inspect) args.andand.collect! do |o| o = Array.wrap o col = Column.new model, o.first o = 'desc' == o.last.to_s.downcase ? :DESC : :ASC - raise NonExistingColumnError.new( [:Column], col) unless col.exist_in? + raise_unless col.exist_in?, NonExistingColumnError.new( [:Column], col) [col, o] end - SmqlToAR.logger.info( {args: args}.inspect) super model, func, args end @@ -302,12 +337,32 @@ class SmqlToAR col, o = o col.joins builder, table t = table + col.path - raise OnlyOrderOnBaseError.new( t) unless 1 == t.length + raise_unless 1 == t.length, RootOnlyFunctionError.new( t) builder.order t, col.col, o end end end + class Limit < Function + Name = :limit + Expected = [Fixnum] + + def build builder, table + raise_unless 1 == table.length, RootOnlyFunctionError.new( table) + builder.limit @args + end + end + + class Offset < Function + Name = :offset + Expected = [Fixnum] + + def build builder, table + raise_unless 1 == table.length, RootOnlyFunctionError.new( table) + builder.offset @args + end + end + def self.new model, col, val SmqlToAR.logger.info( { function: col.first.to_sym }.inspect) r = nil @@ -315,9 +370,7 @@ class SmqlToAR next if [:Function, :Where, :Expected, :Operator].include? c c = const_get c next if Function === c or not c.respond_to?( :try_parse) - SmqlToAR.logger.info( {f: c}.inspect) r = c.try_parse model, col.first.to_sym, val - SmqlToAR.logger.info( {r: r}.inspect) break if r end r diff --git a/lib/smql_to_ar/query_builder.rb b/lib/smql_to_ar/query_builder.rb index 9f6c63e..ab98f8b 100644 --- a/lib/smql_to_ar/query_builder.rb +++ b/lib/smql_to_ar/query_builder.rb @@ -22,23 +22,24 @@ class SmqlToAR class Vid attr_reader :vid def initialize( vid) @vid = vid end - def to_s() ":c#{@vid}" end - def to_sym() "c#{@vid}".to_sym end + def to_s() ":smql_c#{@vid}" end + def to_sym() "smql_c#{@vid}".to_sym end alias sym to_sym def to_i() @vid end end - attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins + attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins, :prefix, :_vid attr_accessor :logger - def initialize model + def initialize model, prefix = nil, base_table + @prefix = "smql" @logger = SmqlToAR.logger @table_alias = Hash.new do |h, k| k = Array.wrap k - h[k] = "smql,#{k.join(',')}" + h[k] = "#{@prefix},#{k.join(',')}" end @_vid, @_where, @_wobs, @model, @quoter = 0, [], {}, model, model.connection - @base_table = [model.table_name.to_sym] + @base_table = base_table.blank? ? [model.table_name.to_sym] : Array.wrap( base_table) @table_alias[ @base_table] = @base_table.first t = quote_table_name @table_alias[ @base_table] @_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [], [], [] @@ -74,32 +75,57 @@ class SmqlToAR end def build_join orig, pretable, table, prekey, key - " JOIN #{quote_table_name orig.to_sym} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} " + " JOIN #{orig} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} " end - def join table, model - return self if @_joined.include? table # Already joined - pretable = table[0...-1] + def sub_join table, col, model, query + pp [:sub_join, table, col. model, query] + prefix, base_table = "#{@prefix}_sub", col.col + join_ table, model, "(#{query.build( prefix, base_table).tap{|q| p :sub_join => q }.ar.to_sql})" + end + + def join_ table, model, query, pretable = nil + pp [:join_, table, model, query] + pretable ||= table[0...-1] @table_model[ table] = model premodel = @table_model[ pretable] t = @table_alias[ table] pt = quote_table_name @table_alias[ table[ 0...-1]] + pp premodel: premodel, table: table refl = premodel.reflections[table.last] - case refl.macro - when :has_many - @_joins += build_join model.table_name, pretable, t, premodel.primary_key, refl.primary_key_name - when :belongs_to - @_joins += build_join model.table_name, pretable, t, refl.primary_key_name, premodel.primary_key - when :has_and_belongs_to_many - jointable = [','] + table - @_joins += build_join refl.options[:join_table], pretable, @table_alias[jointable], premodel.primary_key, refl.primary_key_name - @_joins += build_join model.table_name, jointable, t, refl.association_foreign_key, refl.association_primary_key - else raise BuilderError, "Unkown reflection macro: #{refl.macro.inspect}" + case refl + when ActiveRecord::Reflection::ThroughReflection + through = refl.through_reflection + pp refl: refl + throughtable = table[0...-1]+[through.name.to_sym] + srctable = throughtable+[refl.source_reflection.name] + @table_model[ srctable] = model + @table_alias[ table] = @table_alias[ srctable] + join_ throughtable, through.klass, quote_table_name( through.table_name) + join_ srctable, refl.klass, query, throughtable + when ActiveRecord::Reflection::AssociationReflection + case refl.macro + when :has_many + @_joins += build_join query, pretable, t, premodel.primary_key, refl.primary_key_name + when :belongs_to + @_joins += build_join query, pretable, t, refl.primary_key_name, premodel.primary_key + when :has_and_belongs_to_many + jointable = [','] + table + @_joins += build_join refl.options[:join_table], pretable, @table_alias[jointable], premodel.primary_key, refl.primary_key_name + @_joins += build_join query, jointable, t, refl.association_foreign_key, refl.association_primary_key + else raise BuilderError, "Unkown reflection macro: #{refl.macro.inspect}" + end + else raise BuilderError, "Unkown reflection type: #{refl.class.name}" end - @_joined.push table self end + def join table, model + return self if @_joined.include? table # Already joined + join_ table, model, quote_table_name( model.table_name) + @_joined.push table + end + def includes table @_includes.push table self @@ -114,6 +140,14 @@ class SmqlToAR @_order.push "#{column table, col} #{:DESC == o ? :DESC : :ASC}" end + def limit count + @_limit = count + end + + def offset count + @_offset = count + end + class Dummy def method_missing m, *a, &e #p :dummy => m, :pars => a, :block => e @@ -138,6 +172,9 @@ class SmqlToAR where( where_str, @_wobs). order( @_order.join( ', ')). includes( incls) + @model = @model.limit @_limit if @_limit + @model = @model.offset @_offset if @_offset + @model end def fix_calculate From 93fd3eeda4b8aab4725bd63197bcb47df59e6a5e Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Tue, 27 Sep 2011 16:51:18 +0200 Subject: [PATCH 02/37] pretty_print removed --- lib/smql_to_ar/condition_types.rb | 1 - lib/smql_to_ar/query_builder.rb | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/smql_to_ar/condition_types.rb b/lib/smql_to_ar/condition_types.rb index 6f831b7..045e1a2 100644 --- a/lib/smql_to_ar/condition_types.rb +++ b/lib/smql_to_ar/condition_types.rb @@ -250,7 +250,6 @@ class SmqlToAR def build builder, table @cols.each do |col, sub| t = table+col.to_a - p t: t, sub: sub builder.sub_join t, col, *sub[0..1] sub[2..-1].each &it.build( builder, t) end diff --git a/lib/smql_to_ar/query_builder.rb b/lib/smql_to_ar/query_builder.rb index ab98f8b..a8978bd 100644 --- a/lib/smql_to_ar/query_builder.rb +++ b/lib/smql_to_ar/query_builder.rb @@ -79,24 +79,20 @@ class SmqlToAR end def sub_join table, col, model, query - pp [:sub_join, table, col. model, query] prefix, base_table = "#{@prefix}_sub", col.col - join_ table, model, "(#{query.build( prefix, base_table).tap{|q| p :sub_join => q }.ar.to_sql})" + join_ table, model, "(#{query.build( prefix, base_table).ar.to_sql})" end def join_ table, model, query, pretable = nil - pp [:join_, table, model, query] pretable ||= table[0...-1] @table_model[ table] = model premodel = @table_model[ pretable] t = @table_alias[ table] pt = quote_table_name @table_alias[ table[ 0...-1]] - pp premodel: premodel, table: table refl = premodel.reflections[table.last] case refl when ActiveRecord::Reflection::ThroughReflection through = refl.through_reflection - pp refl: refl throughtable = table[0...-1]+[through.name.to_sym] srctable = throughtable+[refl.source_reflection.name] @table_model[ srctable] = model From 2fdc45d1d5fd6971ba25979fb4f6854a4ad8ca46 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Wed, 5 Oct 2011 13:25:56 +0200 Subject: [PATCH 03/37] "self" and or ============= This lines mean the same: User.smql :self => {:id => 1} User.smql :id => 1 User.smql 'self.id' => 1 User.smql 'self.self.self.self.id' => 1 self is like a reflection to itself. Very userful if you need the new disjunction: User.smql :self => [{:surname => 'Mueller', :givenname => 'Michael'}, {:givenname => 'Horst', :surname => 'Schlemmer'}], :firm => {:name => 'Hotel an der Elbe'} SmqlToAR::Column ---------------- Rejects every `self`, so it is really like no self, but you use `'self'` as like as a simple reflection. You can ask, if it is self via Column#self? SmqlToAR::QueryBuilder ====================== joins: The old `#join`. Renames because SmqlToAR::And and SmqlToAR::Or which has Array#join, so we cannot delegate `#join` to the QueryBuilder. `#where` -------- Now only one argument! Needed for SmqlToAR::And and SmqlToAR::Or. `#build` will generates LEFT OUTER JOINS now. Needed for disjunctions. The most queries will work like before. Problems: User.smql :articles => {} Before it will return all users with articles, now it will return also users without articles. If you want to have only all users with articles, you ask: User.smql :articles => {:id => true} Will fail if id IS NULL, but this should not happen. ;) `SmqlToAR::And` and `SmqlToAR::Or` ================================== `SmqlToAR::QueryBuilder`-proxies. QueryBuilder let them build where-clauses. And will will produce a conjunction and Or a disjunction of course. They delegates all QueryBuilder-methods to QueryBuilder. Only `#where` will stored local and `#build` will do it partial. They have the same superclass: `SmqlToAR::SubBuilder`. The small changes ================= * `SmqlToAR.reload_library`: Reloads SmqlToAR-lib. Useful while development. * `SmqlToAR::ConditionTypes#conditions`: Return all Conditions. `#try_parse` uses it. * Some classes have a new `#inspect`. * `SmqlToAR::ConditionTypes#Exists` / `NotExists`: `{:id => true}` / `{:id => false}`: This object has setted an id or not? `#id` must exists as column of course! Uses `IS NOT NULL` and `IS NULL`. --- lib/smql_to_ar.rb | 38 ++++++--- lib/smql_to_ar/condition_types.rb | 91 +++++++++++++++------ lib/smql_to_ar/query_builder.rb | 128 +++++++++++++++++++++--------- 3 files changed, 184 insertions(+), 73 deletions(-) diff --git a/lib/smql_to_ar.rb b/lib/smql_to_ar.rb index 9e53dcf..34226e9 100644 --- a/lib/smql_to_ar.rb +++ b/lib/smql_to_ar.rb @@ -107,7 +107,9 @@ class SmqlToAR # Model der Relation `rel` von `model` def self.model_of model, rel - model.reflections[ rel.to_sym].andand.klass + rel = rel.to_sym + r = model.reflections[ rel].andand.klass + r.nil? && :self == rel ? model : r end # Eine Spalte in einer Tabelle, relativ zu `Column#model`. @@ -121,7 +123,7 @@ class SmqlToAR def initialize model, *col @model = model @last_model = nil - *@path, @col = Array.wrap( col).collect( &it.to_s.split( /[.\/]/)).flatten.collect( &:to_sym) + *@path, @col = *Array.wrap( col).collect( &it.to_s.split( /[.\/]/)).flatten.collect( &:to_sym).reject( &it==:self) end def last_model @@ -131,9 +133,12 @@ class SmqlToAR def each model = @model @path.each do |rel| - model = SmqlToAR.model_of model, rel - return false unless model - yield rel, model + rel = rel.to_sym + unless :self == rel + model = SmqlToAR.model_of model, rel + return false unless model + yield rel, model + end end model end @@ -158,20 +163,21 @@ class SmqlToAR def joins builder = nil, table = nil, &exe pp = [] table = Array.wrap table - exe ||= builder ? lambda {|j, m| builder.join table+j, m} : Array.method( :[]) + exe ||= builder ? lambda {|j, m| builder.joins table+j, m} : Array.method( :[]) collect do |rel, model| pp.push rel exe.call pp, model end end - def length() @path.length+1 end - def size() @path.size+1 end - def to_a() @path+[@col] end + def self?() !@col end + def length() @path.length+(self.self? ? 0 : 1) end + def size() @path.size+(self.self? ? 0 : 1) end + def to_a() @path+(self.self? ? [] : [@col]) end def to_s() to_a.join '.' end def to_sym() to_s.to_sym end def to_json() to_s end def inspect() "#" end - def relation() SmqlToAR.model_of last_model, @col end + def relation() self.self? ? model : SmqlToAR.model_of( last_model, @col) end def allowed?() ! self.protected? end def child?() @path.empty? and !!relation end end @@ -225,9 +231,9 @@ class SmqlToAR self end - def build prefix = nil, base_table = nil + def build prefix = nil benchmark 'SMQL build query' do - @builder = QueryBuilder.new @model, prefix, base_table + @builder = QueryBuilder.new @model, prefix table = @builder.base_table @conditions.each &it.build( builder, table) end @@ -252,4 +258,12 @@ class SmqlToAR def self.to_ar *params new( *params).to_ar end + + def self.reload_library + lib_dir = File.dirname __FILE__ + fj = lambda {|*a| File.join lib_dir, *a } + load fj.call( 'smql_to_ar.rb') + load fj.call( 'smql_to_ar', 'condition_types.rb') + load fj.call( 'smql_to_ar', 'query_builder.rb') + end end diff --git a/lib/smql_to_ar/condition_types.rb b/lib/smql_to_ar/condition_types.rb index 045e1a2..6061e62 100644 --- a/lib/smql_to_ar/condition_types.rb +++ b/lib/smql_to_ar/condition_types.rb @@ -33,15 +33,29 @@ class SmqlToAR k.split( '|').collect &:to_sym end + def conditions &e + unless block_given? + r = Enumerator.new( self, :conditions) + s = self + r.define_singleton_method :[] do |k| + s.conditions.select {|c| c::Operator === k } + end + return r + end + constants.each do |c| + next if :Condition == c + c = const_get c + next if Condition === c + yield c + end + end + # Eine Regel parsen. # Ex: Person, "givenname=", "Peter" def try_parse_it model, colop, val r = nil #p :try_parse => { :model => model, :colop => colop, :value => val } - constants.each do |c| - next if :Condition == c - c = const_get c - next if Condition === c + conditions.each do |c| raise_unless colop =~ /^(?:\d*:)?(.*?)(\W*)$/, UnexpectedColOpError.new( model, colop, val) col, op = $1, $2 col = split_keys( col).collect {|c| Column.new model, c } @@ -81,11 +95,17 @@ class SmqlToAR Expected = [] Where = nil - # Versuche das Objekt zu erkennen. Operator und Expected muessen passen. - # Passt das Object, die Klasse instanzieren. - def self.try_parse model, cols, op, val - #p :self => name, :try_parse => op, :cols => cols, :with => self::Operator, :value => val, :expected => self::Expected, :model => model.name - new model, cols, val if self::Operator === op and self::Expected.any?( &it === val) + class < name, :try_parse => op, :cols => cols, :with => self::Operator, :value => val, :expected => self::Expected, :model => model.name + new model, cols, val if self::Operator === op and self::Expected.any?( &it === val) + end + + def inspect + "#{self.name}(:operator=>#{self::Operator.inspect}, :expected=>#{self::Expected.inspect}, :where=>#{self::Where.inspect})" + end end def initialize model, cols, val @@ -97,6 +117,10 @@ class SmqlToAR verify end + def inspect + "#<#{self.class.name}:0x#{(self.object_id<<1).to_s 16} model: #{self.class.name}, cols: #{@cols.inspect}, value: #{@value.inspect}>" + end + def verify @cols.each do |col| verify_column col @@ -118,8 +142,8 @@ class SmqlToAR end # Erstelle alle noetigen Klauseln. builder nimmt diese entgegen, - # wobei builder.join, builder.select, builder.where und builder.wobs von interesse sind. - # mehrere Schluessel bedeuten, dass die Values _alle_ zutreffen muessen, wobei die Schluessel geodert werden. + # wobei builder.joins, builder.select, builder.where und builder.wobs von interesse sind. + # mehrere Schluessel bedeuten, dass die Values _alle_ zutreffen muessen, wobei die Schluessel geODERt werden. # Ex: # 1) {"givenname=", "Peter"} #=> givenname = 'Peter' # 2) {"givenname=", ["Peter", "Hans"]} #=> ( givenname = 'Peter' OR givenname = 'Hans' ) @@ -132,11 +156,11 @@ class SmqlToAR @cols.each do |col| col.joins builder, table col = builder.column table+col.path, col.col - builder.where *values.keys.collect {|vid| self.class::Where % [ col, vid.to_s ] } + builder.where values.keys.collect {|vid| self.class::Where % [ col, vid.to_s ] } end else values.keys.each do |vid| - builder.where *@cols.collect {|col| + builder.where @cols.collect {|col| col.joins builder, table col = builder.column table+col.path, col.col self.class::Where % [ col, vid.to_s ] @@ -189,19 +213,26 @@ class SmqlToAR In = simple_condition NotIn, '|=', '%s IN (%s)', [Array] In2 = simple_condition In, '', nil, [Array] - NotEqual = simple_condition Condition, /\!=|<>/, "%s <> %s", [Array, String, Numeric] + NotEqual = simple_condition Condition, '!=', "%s <> %s", [Array, String, Numeric] + NotEqual2 = simple_condition Condition, '<>', "%s <> %s", [Array, String, Numeric] GreaterThanOrEqual = simple_condition Condition, '>=', "%s >= %s", [Array, Numeric] LesserThanOrEqual = simple_condition Condition, '<=', "%s <= %s", [Array, Numeric] + + # Examples: + # { 'articles=>' => { id: 1 } } + # { 'articles=>' => [ { id: 1 }, { id: 2 } ] } class EqualJoin b2 } end + ap '=>' => builder self end end @@ -250,7 +284,7 @@ class SmqlToAR def build builder, table @cols.each do |col, sub| t = table+col.to_a - builder.sub_join t, col, *sub[0..1] + builder.sub_joins t, col, *sub[0..1] sub[2..-1].each &it.build( builder, t) end self @@ -263,8 +297,9 @@ class SmqlToAR LesserThan = simple_condition Condition, '<', "%s < %s", [Array, Numeric] NotIlike = simple_condition Condition, '!~', "%s NOT ILIKE %s", [Array, String] Ilike = simple_condition Condition, '~', "%s ILIKE %s", [Array, String] + Exists = simple_condition Condition, '', '%s IS NOT NULL', [true] + NotExists = simple_condition Condition, '', '%s IS NULL', [false] - ####### No Operator ####### Join = simple_condition EqualJoin, '', nil, [Hash] InRange2 = simple_condition InRange, '', nil, [Range] class Select < Condition @@ -299,9 +334,15 @@ class SmqlToAR Expected = [] attr_reader :model, :func, :args - def self.try_parse model, func, args - SmqlToAR.logger.info( { try_parse: [func,args]}.inspect) - self.new model, func, args if self::Name === func and self::Expected.any?( &it === args) + class <#{self::Name}, :expected=>#{self::Expected})" + end end def initialize model, func, args diff --git a/lib/smql_to_ar/query_builder.rb b/lib/smql_to_ar/query_builder.rb index a8978bd..56ae0c0 100644 --- a/lib/smql_to_ar/query_builder.rb +++ b/lib/smql_to_ar/query_builder.rb @@ -29,30 +29,34 @@ class SmqlToAR end attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins, :prefix, :_vid - attr_accessor :logger + attr_accessor :logger, :limit, :offset - def initialize model, prefix = nil, base_table + def initialize model, prefix = nil @prefix = "smql" @logger = SmqlToAR.logger @table_alias = Hash.new do |h, k| - k = Array.wrap k - h[k] = "#{@prefix},#{k.join(',')}" + j = Array.wrap( k).compact + h[k] = h.key?(j) ? h[j] : "#{@prefix},#{j.join(',')}" end - @_vid, @_where, @_wobs, @model, @quoter = 0, [], {}, model, model.connection - @base_table = base_table.blank? ? [model.table_name.to_sym] : Array.wrap( base_table) + @_vid, @_where, @_wobs, @model, @quoter = 0, SmqlToAR::And[], {}, model, model.connection + @base_table = [model.table_name.to_sym] @table_alias[ @base_table] = @base_table.first t = quote_table_name @table_alias[ @base_table] - @_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [], [], [] + @_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [@base_table], [], [] @table_model = {@base_table => @model} end def vid() Vid.new( @_vid+=1) end + def inspect + "#<#{self.class.name}:#{"0x%x"% (self.object_id<<1)}|#{@prefix}:#{@base_table}:#{@model} vid=#{@_vid} where=#{@_where} wobs=#{@_wobs} select=#{@_select} aliases=#{@_table_alias}>" + end + # Jede via where uebergebene Condition wird geodert und alle zusammen werden geundet. # "Konjunktive Normalform". Allerdings duerfen Conditions auch Komplexe Abfragen enthalten. - # Ex: builder.where( 'a = a', 'b = c').where( 'c = d', 'e = e').where( 'x = y').where( '( m = n AND o = p )', 'f = g') + # Ex: builder.where( ['a = a', 'b = c']).where( ['c = d', 'e = e']).where( 'x = y').where( ['( m = n AND o = p )', 'f = g']) # #=> WHERE ( a = a OR b = c ) AND ( c = d OR e = e ) AND x = y ( ( m = n AND o = p ) OR f = g ) - def where *cond + def where cond @_where.push cond self end @@ -75,12 +79,12 @@ class SmqlToAR end def build_join orig, pretable, table, prekey, key - " JOIN #{orig} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} " + " LEFT OUTER JOIN #{orig} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} " end - def sub_join table, col, model, query - prefix, base_table = "#{@prefix}_sub", col.col - join_ table, model, "(#{query.build( prefix, base_table).ar.to_sql})" + def sub_joins table, col, model, query + prefix, base_table = "#{@prefix}_sub", col.relation.table_name + join_ table, model, "(#{query.build( prefix).ar.to_sql})" end def join_ table, model, query, pretable = nil @@ -116,7 +120,8 @@ class SmqlToAR self end - def join table, model + def joins table, model + table = table.flatten.compact return self if @_joined.include? table # Already joined join_ table, model, quote_table_name( model.table_name) @_joined.push table @@ -136,40 +141,22 @@ class SmqlToAR @_order.push "#{column table, col} #{:DESC == o ? :DESC : :ASC}" end - def limit count - @_limit = count - end - - def offset count - @_offset = count - end - - class Dummy - def method_missing m, *a, &e - #p :dummy => m, :pars => a, :block => e - self - end - end - def build_ar - where_str = @_where.collect do |w| - w = Array.wrap w - 1 == w.length ? w.first : "( #{w.join( ' OR ')} )" - end.join ' AND ' + where_str = @_where.type_correction!.optimize!.tap {|x| p x }.build_where incls = {} @_includes.each do |inc| b = incls inc[1..-1].collect {|rel| b = b[rel] ||= {} } end - @logger.debug incls: incls, joins: @_joins + @logger.info where: where_str, wobs: @_wobs @model = @model. select( @_select.join( ', ')). joins( @_joins). where( where_str, @_wobs). order( @_order.join( ', ')). includes( incls) - @model = @model.limit @_limit if @_limit - @model = @model.offset @_offset if @_offset + @model = @model.limit @limit if @limit + @model = @model.offset @offset if @offset @model end @@ -189,4 +176,73 @@ class SmqlToAR @model end end + + class SubBuilder < Array + attr_reader :parent, :_where + delegate :wobs, :joins, :includes, :sub_joins, :vid, :quote_column_name, :quoter, :quote_table_name, :column, :to => :parent + + def initialize parent, tmp = false + p init: self, parent: parent + @parent = parent + @parent.where self unless @parend.nil? && tmp + end + + def new parent, tmp = false + super parent, tmp + #return parent if self.class == parent.class + #super parent + end + + alias where push + + def type_correction! + collect! do |sub| + if sub.kind_of? Array + sub = default[ *sub] unless sub.respond_to?( :type_correction!) + sub.type_correction! + end + sub + end + self + end + + def optimize! + p optimize: self + ext = [] + collect! do |sub| + sub = sub.optimize! if sub.kind_of? Array + if self.class == sub.class + ext.push *sub + nil + elsif sub.blank? + nil + else + sub + end + end.compact! + p optimized: self + p ext: ext + push *ext + self + end + + def inspect + "#{self.class.name.sub( /.*::/, '')}[ #{collect(&:inspect).join ', '}]" + end + def default() SmqlToAR::And end + def default_new( parent) default.new self, parent, false end + end + + class And < SubBuilder + def default; SmqlToAR::Or; end + def build_where + join ' AND ' + end + end + + class Or < SubBuilder + def build_where + join ' OR ' + end + end end From 75c314159baa7780187827217b99e83fd7d32da3 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Wed, 5 Oct 2011 14:06:50 +0200 Subject: [PATCH 04/37] v0.0.4 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index bcab45a..81340c7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.3 +0.0.4 From 09dfa782a833b5a76ad092602eff64fb21e79ab5 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Wed, 5 Oct 2011 14:53:34 +0200 Subject: [PATCH 05/37] debugging-output removed --- lib/smql_to_ar/condition_types.rb | 4 ---- lib/smql_to_ar/query_builder.rb | 7 +------ 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/lib/smql_to_ar/condition_types.rb b/lib/smql_to_ar/condition_types.rb index 6061e62..1ab5b0f 100644 --- a/lib/smql_to_ar/condition_types.rb +++ b/lib/smql_to_ar/condition_types.rb @@ -229,7 +229,6 @@ class SmqlToAR super( *pars) @value = Array.wrap @value cols = {} - p self: self, cols: @cols @cols.each do |col| col_model = col.relation cols[col] = [col_model] + @value.collect {|val| ConditionTypes.try_parse( col_model, val) } @@ -250,7 +249,6 @@ class SmqlToAR b2 = 1 == sub.length ? builder : Or.new( builder) sub.each {|i| i.collect( &it.build( And.new( b2), t)); p 'or' => b2 } end - ap '=>' => builder self end end @@ -336,7 +334,6 @@ class SmqlToAR class < :parent def initialize parent, tmp = false - p init: self, parent: parent @parent = parent @parent.where self unless @parend.nil? && tmp end @@ -207,7 +205,6 @@ class SmqlToAR end def optimize! - p optimize: self ext = [] collect! do |sub| sub = sub.optimize! if sub.kind_of? Array @@ -220,8 +217,6 @@ class SmqlToAR sub end end.compact! - p optimized: self - p ext: ext push *ext self end From 863ebca9cd02a292f6f646e23bf51d37716aa12a Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Thu, 6 Oct 2011 13:24:17 +0200 Subject: [PATCH 06/37] fixes: OR... --- lib/smql_to_ar/condition_types.rb | 12 +++++++----- lib/smql_to_ar/query_builder.rb | 9 ++++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/smql_to_ar/condition_types.rb b/lib/smql_to_ar/condition_types.rb index 1ab5b0f..117f629 100644 --- a/lib/smql_to_ar/condition_types.rb +++ b/lib/smql_to_ar/condition_types.rb @@ -159,12 +159,13 @@ class SmqlToAR builder.where values.keys.collect {|vid| self.class::Where % [ col, vid.to_s ] } end else + b2 = SmqlToAR::And.new builder values.keys.each do |vid| - builder.where @cols.collect {|col| + b2.where SmqlToAR::Or[ *@cols.collect {|col| col.joins builder, table col = builder.column table+col.path, col.col self.class::Where % [ col, vid.to_s ] - } + }] end end self @@ -272,7 +273,7 @@ class SmqlToAR def initialize model, cols, val super model, cols, val[1] # sub: model, subquery, sub(condition) - @cols.each {|col, sub| sub[ 1...1] = SmqlToAR.new( col.relation, val[0]).parse } + @cols.each {|col, sub| sub[ 1..-1] = SmqlToAR.new( col.relation, val[0]).parse, *sub[-1] } end def verify_column col @@ -283,6 +284,7 @@ class SmqlToAR @cols.each do |col, sub| t = table+col.to_a builder.sub_joins t, col, *sub[0..1] + #ap sub: sub[2..-1] sub[2..-1].each &it.build( builder, t) end self @@ -386,7 +388,7 @@ class SmqlToAR def build builder, table raise_unless 1 == table.length, RootOnlyFunctionError.new( table) - builder.limit @args + builder.limit = Array.wrap(@args).first.to_i end end @@ -396,7 +398,7 @@ class SmqlToAR def build builder, table raise_unless 1 == table.length, RootOnlyFunctionError.new( table) - builder.offset @args + builder.offset = Array.wrap(@args).first.to_i end end diff --git a/lib/smql_to_ar/query_builder.rb b/lib/smql_to_ar/query_builder.rb index 9123fb3..e38eb82 100644 --- a/lib/smql_to_ar/query_builder.rb +++ b/lib/smql_to_ar/query_builder.rb @@ -79,7 +79,7 @@ class SmqlToAR end def build_join orig, pretable, table, prekey, key - " LEFT OUTER JOIN #{orig} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} " + " LEFT JOIN #{orig} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} " end def sub_joins table, col, model, query @@ -226,18 +226,21 @@ class SmqlToAR end def default() SmqlToAR::And end def default_new( parent) default.new self, parent, false end + def collect_build_where + collect {|x| x.respond_to?( :build_where) ? x.build_where : x.to_s } + end end class And < SubBuilder def default; SmqlToAR::Or; end def build_where - join ' AND ' + collect_build_where.join ' AND ' end end class Or < SubBuilder def build_where - join ' OR ' + collect_build_where.join ' OR ' end end end From cc49fdefe91b654f07b20a735f591bb854b83dda Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Thu, 6 Oct 2011 13:24:44 +0200 Subject: [PATCH 07/37] v0.0.4.1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 81340c7..1eb4fbd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.4 +0.0.4.1 From 11466211acafc263a48ad25a730e22c65669a39a Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Thu, 6 Oct 2011 13:27:41 +0200 Subject: [PATCH 08/37] group and having clause init --- lib/smql_to_ar.rb | 6 ++++ lib/smql_to_ar/condition_types.rb | 59 ++++++++++++++++++++++++++++++- lib/smql_to_ar/query_builder.rb | 9 +++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/lib/smql_to_ar.rb b/lib/smql_to_ar.rb index 34226e9..2f18ab5 100644 --- a/lib/smql_to_ar.rb +++ b/lib/smql_to_ar.rb @@ -101,6 +101,12 @@ class SmqlToAR end end + class UnknownHavingMethod < SMQLError + def initialize expected, got + super :expected => expected, :got => got + end + end + class BuilderError < Exception; end ############################################################################# diff --git a/lib/smql_to_ar/condition_types.rb b/lib/smql_to_ar/condition_types.rb index 117f629..0ef2a69 100644 --- a/lib/smql_to_ar/condition_types.rb +++ b/lib/smql_to_ar/condition_types.rb @@ -382,13 +382,70 @@ class SmqlToAR end end + class GroupBy < Function + Name = :group + Excepted = [String, Array] + + def initialize model, func, args + super model, func, args.collect( &Columnt.method( :new)) + end + + def build builder, table + return if @args.blank? + @args.each do |col| + t = Column.new *(table + col.to_a) + raise_unless 1 == t.length, RootOnlyFunctionError.new( t) + builder.group t + end + end + end + + class Having < Function + Name = :having + Excepted = [Array] + + def initialize model, func, args + args = Hash[ *args.collect do |col, meth| + col = Column.new col + case meth.to_sym + when :max, :min + else raise UnknownHavingMethod.new( [:max, :min], meth) + end + [col, meth] + end] + super model, func, args + end + + def build builder, table + return if @args.blank? + @args.each do |col, meth| + t = Column.new *(table + col.to_a) + raise_unless 1 == t.length, RootOnlyFunctionError.new( t) + builder.having meth => t + end + end + end + + class Max < Having + Name = :max + Expected = [String] + + def initialize model, func, args + super model, :having, Hash[ *args.collect {|col| [col, func] } ] + end + end + + class Min < Max + Name = :min + end + class Limit < Function Name = :limit Expected = [Fixnum] def build builder, table raise_unless 1 == table.length, RootOnlyFunctionError.new( table) - builder.limit = Array.wrap(@args).first.to_i + builder.limit = Array.wrap( @args).first.to_i end end diff --git a/lib/smql_to_ar/query_builder.rb b/lib/smql_to_ar/query_builder.rb index e38eb82..17fbc75 100644 --- a/lib/smql_to_ar/query_builder.rb +++ b/lib/smql_to_ar/query_builder.rb @@ -43,6 +43,7 @@ class SmqlToAR @table_alias[ @base_table] = @base_table.first t = quote_table_name @table_alias[ @base_table] @_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [@base_table], [], [] + @_group, @_having = {}, {} @table_model = {@base_table => @model} end @@ -66,6 +67,14 @@ class SmqlToAR self end + def group col + @_group.push col + end + + def having h + @_having.update h + end + def quote_column_name name @quoter.quote_column_name( name).gsub /"\."/, ',' end From d5aa6052ec08b49ad4f0232e847c61737a8269f8 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Thu, 6 Oct 2011 15:28:40 +0200 Subject: [PATCH 09/37] lesser debug --- lib/smql_to_ar/condition_types.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/smql_to_ar/condition_types.rb b/lib/smql_to_ar/condition_types.rb index 0ef2a69..87927ba 100644 --- a/lib/smql_to_ar/condition_types.rb +++ b/lib/smql_to_ar/condition_types.rb @@ -248,7 +248,7 @@ class SmqlToAR col.joins.each {|j, m| builder.joins table+j, m } builder.joins t, model b2 = 1 == sub.length ? builder : Or.new( builder) - sub.each {|i| i.collect( &it.build( And.new( b2), t)); p 'or' => b2 } + sub.each {|i| i.collect( &it.build( And.new( b2), t)) } end self end From f3bcdd4b2561cd868c4a61069a3a3a93398668ee Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Tue, 25 Oct 2011 16:31:18 +0200 Subject: [PATCH 10/37] has_one-reflection (like has_many), group & having. subqueries deactivated --- lib/smql_to_ar.rb | 6 --- lib/smql_to_ar/condition_types.rb | 67 +++---------------------------- lib/smql_to_ar/query_builder.rb | 11 +---- 3 files changed, 7 insertions(+), 77 deletions(-) diff --git a/lib/smql_to_ar.rb b/lib/smql_to_ar.rb index 2f18ab5..34226e9 100644 --- a/lib/smql_to_ar.rb +++ b/lib/smql_to_ar.rb @@ -101,12 +101,6 @@ class SmqlToAR end end - class UnknownHavingMethod < SMQLError - def initialize expected, got - super :expected => expected, :got => got - end - end - class BuilderError < Exception; end ############################################################################# diff --git a/lib/smql_to_ar/condition_types.rb b/lib/smql_to_ar/condition_types.rb index 87927ba..c7f0530 100644 --- a/lib/smql_to_ar/condition_types.rb +++ b/lib/smql_to_ar/condition_types.rb @@ -248,7 +248,7 @@ class SmqlToAR col.joins.each {|j, m| builder.joins table+j, m } builder.joins t, model b2 = 1 == sub.length ? builder : Or.new( builder) - sub.each {|i| i.collect( &it.build( And.new( b2), t)) } + sub.each {|i| i.collect( &it.build( And.new( b2), t)); p 'or' => b2 } end self end @@ -266,6 +266,7 @@ class SmqlToAR # is, second is not allowed (limit and order must be in root) and this means something like # "Person must have the Article owned by Person which has 'some text' in content. # limit and order has no function in this query and this article needn't to be the last." +=begin class SubEqualJoin < EqualJoin Operator = '()' Expected = [lambda {|x| x.kind_of?( Array) and (1..2).include?( x.length) and x.all?( &it.kind_of?( Hash))}] @@ -290,6 +291,7 @@ class SmqlToAR self end end +=end Equal = simple_condition Condition, '=', "%s = %s", [Array, String, Numeric] Equal2 = simple_condition Equal, '', "%s = %s", [String, Numeric] @@ -297,8 +299,8 @@ class SmqlToAR LesserThan = simple_condition Condition, '<', "%s < %s", [Array, Numeric] NotIlike = simple_condition Condition, '!~', "%s NOT ILIKE %s", [Array, String] Ilike = simple_condition Condition, '~', "%s ILIKE %s", [Array, String] - Exists = simple_condition Condition, '', '%s IS NOT NULL', [true] - NotExists = simple_condition Condition, '', '%s IS NULL', [false] + Exists = simple_condition Condition, '', '%s IS NOT NULL', [TrueClass] + NotExists = simple_condition Condition, '', '%s IS NULL', [FalseClass] Join = simple_condition EqualJoin, '', nil, [Hash] InRange2 = simple_condition InRange, '', nil, [Range] @@ -382,70 +384,13 @@ class SmqlToAR end end - class GroupBy < Function - Name = :group - Excepted = [String, Array] - - def initialize model, func, args - super model, func, args.collect( &Columnt.method( :new)) - end - - def build builder, table - return if @args.blank? - @args.each do |col| - t = Column.new *(table + col.to_a) - raise_unless 1 == t.length, RootOnlyFunctionError.new( t) - builder.group t - end - end - end - - class Having < Function - Name = :having - Excepted = [Array] - - def initialize model, func, args - args = Hash[ *args.collect do |col, meth| - col = Column.new col - case meth.to_sym - when :max, :min - else raise UnknownHavingMethod.new( [:max, :min], meth) - end - [col, meth] - end] - super model, func, args - end - - def build builder, table - return if @args.blank? - @args.each do |col, meth| - t = Column.new *(table + col.to_a) - raise_unless 1 == t.length, RootOnlyFunctionError.new( t) - builder.having meth => t - end - end - end - - class Max < Having - Name = :max - Expected = [String] - - def initialize model, func, args - super model, :having, Hash[ *args.collect {|col| [col, func] } ] - end - end - - class Min < Max - Name = :min - end - class Limit < Function Name = :limit Expected = [Fixnum] def build builder, table raise_unless 1 == table.length, RootOnlyFunctionError.new( table) - builder.limit = Array.wrap( @args).first.to_i + builder.limit = Array.wrap(@args).first.to_i end end diff --git a/lib/smql_to_ar/query_builder.rb b/lib/smql_to_ar/query_builder.rb index 17fbc75..0e8d4c2 100644 --- a/lib/smql_to_ar/query_builder.rb +++ b/lib/smql_to_ar/query_builder.rb @@ -43,7 +43,6 @@ class SmqlToAR @table_alias[ @base_table] = @base_table.first t = quote_table_name @table_alias[ @base_table] @_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [@base_table], [], [] - @_group, @_having = {}, {} @table_model = {@base_table => @model} end @@ -67,14 +66,6 @@ class SmqlToAR self end - def group col - @_group.push col - end - - def having h - @_having.update h - end - def quote_column_name name @quoter.quote_column_name( name).gsub /"\."/, ',' end @@ -114,7 +105,7 @@ class SmqlToAR join_ srctable, refl.klass, query, throughtable when ActiveRecord::Reflection::AssociationReflection case refl.macro - when :has_many + when :has_many, :has_one @_joins += build_join query, pretable, t, premodel.primary_key, refl.primary_key_name when :belongs_to @_joins += build_join query, pretable, t, refl.primary_key_name, premodel.primary_key From f9ee0754f14c00e3e8e1adbdd17d642cd3b8a64a Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Thu, 27 Oct 2011 14:26:53 +0200 Subject: [PATCH 11/37] Version 0.0.4.2 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 1eb4fbd..1668587 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.4.1 +0.0.4.2 From 5c68b74ee4865af5ddbc0487af1d88f6e6824d5e Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Thu, 27 Oct 2011 15:21:56 +0200 Subject: [PATCH 12/37] lesser logging. order-bug fixed --- lib/smql_to_ar.rb | 16 ++++++++-------- lib/smql_to_ar/condition_types.rb | 16 ++++++++-------- lib/smql_to_ar/query_builder.rb | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/smql_to_ar.rb b/lib/smql_to_ar.rb index 63e1a10..d144320 100644 --- a/lib/smql_to_ar.rb +++ b/lib/smql_to_ar.rb @@ -180,35 +180,35 @@ class SmqlToAR end def parse - benchmark 'SMQL parse' do + #benchmark 'SMQL parse' do @conditions = ConditionTypes.try_parse @model, @query - end + #end #p conditions: @conditions self end def build - benchmark 'SMQL build query' do + #benchmark 'SMQL build query' do @builder = QueryBuilder.new @model table = @builder.base_table @conditions.each {|condition| condition.build builder, table } - end + #end #p builder: @builder self end def ar - @ar ||= benchmark 'SMQL ar' do + @ar ||= #benchmark 'SMQL ar' do @builder.to_ar - end + #end end def to_ar - benchmark 'SMQL' do + #benchmark 'SMQL' do parse build ar - end + #end end def self.to_ar *params diff --git a/lib/smql_to_ar/condition_types.rb b/lib/smql_to_ar/condition_types.rb index bcf21cb..84fb850 100644 --- a/lib/smql_to_ar/condition_types.rb +++ b/lib/smql_to_ar/condition_types.rb @@ -213,7 +213,7 @@ class SmqlToAR @cols.each do |col, sub| t = table + col.path + [col.col] #p sub: sub - p col: col, joins: col.joins + #p col: col, joins: col.joins col.joins.each {|j, m| builder.join table+j, m } builder.join t, SmqlToAR.model_of( col.last_model, col.col) sub[1..-1].each {|one| one.build builder, t } @@ -263,7 +263,7 @@ class SmqlToAR attr_reader :model, :func, :args def self.try_parse model, func, args - SmqlToAR.logger.info( { try_parse: [func,args]}.inspect) + #SmqlToAR.logger.info( { try_parse: [func,args]}.inspect) self.new model, func, args if self::Name === func and self::Expected.any? {|e| e === args } end @@ -277,14 +277,14 @@ class SmqlToAR Expected = [String, Array, Hash, nil] def initialize model, func, args - SmqlToAR.logger.info( {args: args}.inspect) + #SmqlToAR.logger.info( {args: args}.inspect) args = case args when String then [args] when Array, Hash then args.to_a when nil then nil else raise 'Oops' end - SmqlToAR.logger.info( {args: args}.inspect) + #SmqlToAR.logger.info( {args: args}.inspect) args.andand.collect! do |o| o = Array.wrap o col = Column.new model, o.first @@ -292,7 +292,7 @@ class SmqlToAR raise NonExistingColumnError.new( [:Column], col) unless col.exist_in? [col, o] end - SmqlToAR.logger.info( {args: args}.inspect) + #SmqlToAR.logger.info( {args: args}.inspect) super model, func, args end @@ -309,15 +309,15 @@ class SmqlToAR end def self.new model, col, val - SmqlToAR.logger.info( { function: col.first.to_sym }.inspect) + #SmqlToAR.logger.info( { function: col.first.to_sym }.inspect) r = nil constants.each do |c| next if [:Function, :Where, :Expected, :Operator].include? c c = const_get c next if Function === c or not c.respond_to?( :try_parse) - SmqlToAR.logger.info( {f: c}.inspect) + #SmqlToAR.logger.info( {f: c}.inspect) r = c.try_parse model, col.first.to_sym, val - SmqlToAR.logger.info( {r: r}.inspect) + #SmqlToAR.logger.info( {r: r}.inspect) break if r end r diff --git a/lib/smql_to_ar/query_builder.rb b/lib/smql_to_ar/query_builder.rb index 0eb06a5..dc3bd4e 100644 --- a/lib/smql_to_ar/query_builder.rb +++ b/lib/smql_to_ar/query_builder.rb @@ -111,7 +111,7 @@ class SmqlToAR end def order table, col, o - tc = column table, col + ct = column table, col @_select.push ct @_order.push "#{ct} #{:DESC == o ? :DESC : :ASC}" self From 3b60648ee79b965a12c7435944e7e16ec1974669 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Thu, 27 Oct 2011 15:22:49 +0200 Subject: [PATCH 13/37] Version 0.0.4.3 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 1668587..2ec68a9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.4.2 +0.0.4.3 From 69aceef180a7754420c675e140e229bc4beb8f7d Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Mon, 21 Nov 2011 10:06:46 +0100 Subject: [PATCH 14/37] aliased tables. firm: { "people[users]": {type: "User"}, "people[old]": {"age>:": 50 } # Every ToDo which has a User as Person or people older than 50. It is not an OR, it is an AND, but {people:{type:"User","age>":50}} or {"people":{type:"User"},"people":{"age>:":50} means every User older than 50. "users" and "old" are independet questions and both must be present, if firm should be true. --- lib/smql_to_ar.rb | 47 +++++++++++++++++++++++++++---- lib/smql_to_ar/condition_types.rb | 5 +++- lib/smql_to_ar/query_builder.rb | 37 ++++++++++++++++-------- 3 files changed, 70 insertions(+), 19 deletions(-) diff --git a/lib/smql_to_ar.rb b/lib/smql_to_ar.rb index 6d252af..1eedbd6 100644 --- a/lib/smql_to_ar.rb +++ b/lib/smql_to_ar.rb @@ -107,9 +107,9 @@ class SmqlToAR # Model der Relation `rel` von `model` def self.model_of model, rel - rel = rel.to_sym - r = model.reflections[ rel].andand.klass - r.nil? && :self == rel ? model : r + p model_of: rel.to_sym, model: model + r = model.reflections[ rel.to_sym].andand.klass + r.nil? && rel === :self ? model : r end # Eine Spalte in einer Tabelle, relativ zu `Column#model`. @@ -120,10 +120,43 @@ class SmqlToAR attr_reader :path, :col attr_accessor :model + class Col + attr_accessor :col, :as + def initialize col, as = nil + if col.kind_of? Col + @col, @as = col.col, col.as + elsif /^(.*?)\[(.*)\]$/ =~ col.to_s + @col, @as = $1, $2 + else + @col, @as = col.to_s, as.to_s + @as = nil if @as.blank? + end + end + + def to_s() @col end + def to_sym() to_s.to_sym end + def inspect() "#{@col}[@#{@as}]" end + def to_alias() "#{@col}[#{@as}]" end + + def == other + other = Col.new other + @col = other.col && @as == other.col + end + + def === other + other = Col.new other + @col == other.col + end + end + def initialize model, *col @model = model @last_model = nil - *@path, @col = *Array.wrap( col).collect( &it.to_s.split( /[.\/]/)).flatten.collect( &:to_sym).reject( &it==:self) + *@path, @col = *Array.wrap( col). + collect( &it.to_s.split( /[.\/]/)). + flatten. + collect( &Col.method( :new)). + reject( &it===:self) end def last_model @@ -132,9 +165,10 @@ class SmqlToAR def each model = @model + p each: self, path: @path @path.each do |rel| - rel = rel.to_sym - unless :self == rel + p rel: rel + unless rel === :self model = SmqlToAR.model_of model, rel return false unless model yield rel, model @@ -169,6 +203,7 @@ class SmqlToAR exe.call pp, model end end + def self?() !@col end def length() @path.length+(self.self? ? 0 : 1) end def size() @path.size+(self.self? ? 0 : 1) end diff --git a/lib/smql_to_ar/condition_types.rb b/lib/smql_to_ar/condition_types.rb index 05ce5c6..d91b86a 100644 --- a/lib/smql_to_ar/condition_types.rb +++ b/lib/smql_to_ar/condition_types.rb @@ -56,9 +56,11 @@ class SmqlToAR r = nil #p :try_parse => { :model => model, :colop => colop, :value => val } conditions.each do |c| - raise_unless colop =~ /^(?:\d*:)?(.*?)(\W*)$/, UnexpectedColOpError.new( model, colop, val) + raise_unless colop =~ /^(?:\d*:)?(.*?)((?:\W*(?!\])\W)?)$/, UnexpectedColOpError.new( model, colop, val) col, op = $1, $2 + p col: col, op: op col = split_keys( col).collect {|c| Column.new model, c } + p col: col r = c.try_parse model, col, op, val break if r end @@ -246,6 +248,7 @@ class SmqlToAR model, *sub = sub t = table + col.path + [col.col] col.joins.each {|j, m| builder.joins table+j, m } + p build_t: t builder.joins t, model b2 = 1 == sub.length ? builder : Or.new( builder) sub.each {|i| i.collect( &it.build( And.new( b2), t)); p 'or' => b2 } diff --git a/lib/smql_to_ar/query_builder.rb b/lib/smql_to_ar/query_builder.rb index ff3980d..76a6f8f 100644 --- a/lib/smql_to_ar/query_builder.rb +++ b/lib/smql_to_ar/query_builder.rb @@ -28,20 +28,27 @@ class SmqlToAR def to_i() @vid end end + class Aliases < Hash + def self.new prefix, *a, &e + e ||= lambda do |h, k| + j = Array.wrap( k).compact + h[k] = h.key?(j) ? h[j] : "#{prefix},#{j.collect( &:to_alias).join( ',')}" + end + super *a, &e + end + end + attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins, :prefix, :_vid attr_accessor :logger, :limit, :offset def initialize model, prefix = nil @prefix = "smql" @logger = SmqlToAR.logger - @table_alias = Hash.new do |h, k| - j = Array.wrap( k).compact - h[k] = h.key?(j) ? h[j] : "#{@prefix},#{j.join(',')}" - end + @table_alias = Aliases.new @prefix @_vid, @_where, @_wobs, @model, @quoter = 0, SmqlToAR::And[], {}, model, model.connection - @base_table = [model.table_name.to_sym] + @base_table = [Column::Col.new( model.table_name)] @table_alias[ @base_table] = @base_table.first - t = quote_table_name @table_alias[ @base_table] + t = quote_table_name @base_table.first.col @_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [@base_table], [], [] @table_model = {@base_table => @model} end @@ -49,7 +56,7 @@ class SmqlToAR def vid() Vid.new( @_vid+=1) end def inspect - "#<#{self.class.name}:#{"0x%x"% (self.object_id<<1)}|#{@prefix}:#{@base_table}:#{@model} vid=#{@_vid} where=#{@_where} wobs=#{@_wobs} select=#{@_select} aliases=#{@_table_alias}>" + "#<#{self.class.name}:#{"0x%x"% (self.object_id<<1)}|#{@prefix}:#{@base_table}:#{@model} vid=#{@_vid} where=#{@_where} wobs=#{@_wobs} select=#{@_select} aliases=#{@table_alias}>" end # Jede via where uebergebene Condition wird geodert und alle zusammen werden geundet. @@ -71,14 +78,19 @@ class SmqlToAR end def quote_table_name name + name = case name + when Array, Column::Col then @table_alias[Array.wrap name] + else name.to_s + end @quoter.quote_table_name( name).gsub /"\."/, ',' end def column table, name - "#{quote_table_name table.kind_of?(String) ? table : @table_alias[table]}.#{quote_column_name name}" + "#{quote_table_name table}.#{quote_column_name name}" end def build_join orig, pretable, table, prekey, key + p build_join: {orig: orig, pretable: pretable, table: table, prekey: prekey, key: key}, alias: @table_alias " LEFT JOIN #{orig} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} " end @@ -92,13 +104,14 @@ class SmqlToAR @table_model[ table] = model premodel = @table_model[ pretable] t = @table_alias[ table] - pt = quote_table_name @table_alias[ table[ 0...-1]] - refl = premodel.reflections[table.last] + pt = quote_table_name table[ 0...-1] + p table: table + refl = premodel.reflections[table.last.to_sym] case refl when ActiveRecord::Reflection::ThroughReflection through = refl.through_reflection - throughtable = table[0...-1]+[through.name.to_sym] - srctable = throughtable+[refl.source_reflection.name] + throughtable = table[0...-1]+[Column::Col.new( through.name, table.last.as)] + srctable = throughtable+[Column::Col.new( refl.source_reflection.name, table.last.as)] @table_model[ srctable] = model @table_alias[ table] = @table_alias[ srctable] join_ throughtable, through.klass, quote_table_name( through.table_name) From 63be31734b37f3f8e8cc2a7c65ba54902d760e32 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Tue, 22 Nov 2011 12:14:22 +0100 Subject: [PATCH 15/37] debugging removed --- lib/smql_to_ar.rb | 3 --- lib/smql_to_ar/condition_types.rb | 5 +---- lib/smql_to_ar/query_builder.rb | 2 -- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/smql_to_ar.rb b/lib/smql_to_ar.rb index 1eedbd6..fdf32e9 100644 --- a/lib/smql_to_ar.rb +++ b/lib/smql_to_ar.rb @@ -107,7 +107,6 @@ class SmqlToAR # Model der Relation `rel` von `model` def self.model_of model, rel - p model_of: rel.to_sym, model: model r = model.reflections[ rel.to_sym].andand.klass r.nil? && rel === :self ? model : r end @@ -165,9 +164,7 @@ class SmqlToAR def each model = @model - p each: self, path: @path @path.each do |rel| - p rel: rel unless rel === :self model = SmqlToAR.model_of model, rel return false unless model diff --git a/lib/smql_to_ar/condition_types.rb b/lib/smql_to_ar/condition_types.rb index d91b86a..7349a10 100644 --- a/lib/smql_to_ar/condition_types.rb +++ b/lib/smql_to_ar/condition_types.rb @@ -58,9 +58,7 @@ class SmqlToAR conditions.each do |c| raise_unless colop =~ /^(?:\d*:)?(.*?)((?:\W*(?!\])\W)?)$/, UnexpectedColOpError.new( model, colop, val) col, op = $1, $2 - p col: col, op: op col = split_keys( col).collect {|c| Column.new model, c } - p col: col r = c.try_parse model, col, op, val break if r end @@ -248,10 +246,9 @@ class SmqlToAR model, *sub = sub t = table + col.path + [col.col] col.joins.each {|j, m| builder.joins table+j, m } - p build_t: t builder.joins t, model b2 = 1 == sub.length ? builder : Or.new( builder) - sub.each {|i| i.collect( &it.build( And.new( b2), t)); p 'or' => b2 } + sub.each {|i| i.collect( &it.build( And.new( b2), t)) } end self end diff --git a/lib/smql_to_ar/query_builder.rb b/lib/smql_to_ar/query_builder.rb index 76a6f8f..d6db390 100644 --- a/lib/smql_to_ar/query_builder.rb +++ b/lib/smql_to_ar/query_builder.rb @@ -90,7 +90,6 @@ class SmqlToAR end def build_join orig, pretable, table, prekey, key - p build_join: {orig: orig, pretable: pretable, table: table, prekey: prekey, key: key}, alias: @table_alias " LEFT JOIN #{orig} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} " end @@ -105,7 +104,6 @@ class SmqlToAR premodel = @table_model[ pretable] t = @table_alias[ table] pt = quote_table_name table[ 0...-1] - p table: table refl = premodel.reflections[table.last.to_sym] case refl when ActiveRecord::Reflection::ThroughReflection From 1d6baf41e3439fe79bc1b5ccba04cf7c09269f8c Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Tue, 22 Nov 2011 12:15:36 +0100 Subject: [PATCH 16/37] 0.0.4.4 unstable? --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 2ec68a9..8f570b6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.4.3 +0.0.4.4 From 080237c5d71d2aecdd5af69e5ebd4374e42c5fda Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Mon, 28 Nov 2011 15:47:48 +0100 Subject: [PATCH 17/37] bugfixes: ranges, vid===Symbol, And/Or in NotInRange --- TODO | 2 ++ lib/smql_to_ar/condition_types.rb | 19 +++++++++++++------ lib/smql_to_ar/query_builder.rb | 3 +++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/TODO b/TODO index 28c5c5c..e2a2eb8 100644 --- a/TODO +++ b/TODO @@ -1 +1,3 @@ 1) gem-plugin +2) deprecated-warnings rails 3.1: + * DEPRECATION WARNING: primary_key_name is deprecated and will be removed from Rails 3.2 (use foreign_key instead). diff --git a/lib/smql_to_ar/condition_types.rb b/lib/smql_to_ar/condition_types.rb index 7349a10..639c9bf 100644 --- a/lib/smql_to_ar/condition_types.rb +++ b/lib/smql_to_ar/condition_types.rb @@ -99,7 +99,7 @@ class SmqlToAR # Versuche das Objekt zu erkennen. Operator und Expected muessen passen. # Passt das Object, die Klasse instanzieren. def try_parse model, cols, op, val - #p :self => name, :try_parse => op, :cols => cols, :with => self::Operator, :value => val, :expected => self::Expected, :model => model.name + #p :class => self, :self => name, :try_parse => op, :cols => cols, :with => self::Operator, :value => val, :expected => self::Expected, :model => model.name new model, cols, val if self::Operator === op and self::Expected.any?( &it === val) end @@ -109,6 +109,7 @@ class SmqlToAR end def initialize model, cols, val + #p init: self, caller: caller @model, @cols = model, cols @value = case val when Hash, Range then val @@ -151,7 +152,7 @@ class SmqlToAR # #=> ( givenname = 'Peter' OR surname = 'Peter' ) AND ( givenname = 'Mueller' OR surname = 'Mueller' ) def build builder, table values = Hash[ @value.collect {|value| [ builder.vid, value ] } ] - values.each {|k, v| builder.wobs k.sym => v } + values.each {|k, v| builder.wobs k.to_sym => v } if 1 == @cols.length @cols.each do |col| col.joins builder, table @@ -177,7 +178,7 @@ class SmqlToAR Where = "%s NOT BETWEEN %s AND %s" Expected = [Range, lambda {|val| Array === val && 2 == val.length } ] - def initialze model, cols, val + def initialize model, cols, val if Array === val && 2 == val.length f, l = val f, l = Time.parse(f), Time.parse(l) if f.kind_of? String @@ -187,7 +188,7 @@ class SmqlToAR end def build builder, table - builder.wobs (v1 = builder.vid) => @value.begin, (v2 = builder.vid) => @value.end + builder.wobs (v1 = builder.vid).to_sym => @value.begin, (v2 = builder.vid).to_sym => @value.end @cols.each do |col| col.joins builder, table builder.where self.class::Where % [ builder.column( table+col.path, col.col), v1, v2] @@ -242,13 +243,19 @@ class SmqlToAR end def build builder, table + if 2 < @cols.first.second.length + b2, b3 = And, Or + else + b2, b3 = Or, And + end + b2 = b2.new builder @cols.each do |col, sub| model, *sub = sub t = table + col.path + [col.col] col.joins.each {|j, m| builder.joins table+j, m } builder.joins t, model - b2 = 1 == sub.length ? builder : Or.new( builder) - sub.each {|i| i.collect( &it.build( And.new( b2), t)) } + b4 = b3.new( b2) + sub.each {|i| i.collect( &it.build( And.new( b4), t)) } end self end diff --git a/lib/smql_to_ar/query_builder.rb b/lib/smql_to_ar/query_builder.rb index d6db390..ded1481 100644 --- a/lib/smql_to_ar/query_builder.rb +++ b/lib/smql_to_ar/query_builder.rb @@ -26,6 +26,9 @@ class SmqlToAR def to_sym() "smql_c#{@vid}".to_sym end alias sym to_sym def to_i() @vid end + def === other + to_s === other || to_sym === other || to_i === other || self == other || self === other + end end class Aliases < Hash From 293909b58b2af16a8596573426bda639d921d838 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Mon, 28 Nov 2011 15:48:13 +0100 Subject: [PATCH 18/37] v0.0.4.5 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 8f570b6..3c8f915 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.4.4 +0.0.4.5 From 1c97319d9c6b8268967254bcf2beb7157f7ff30d Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Tue, 29 Nov 2011 12:00:40 +0100 Subject: [PATCH 19/37] Column::Col-fixes (==, ===) --- lib/smql_to_ar.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/smql_to_ar.rb b/lib/smql_to_ar.rb index fdf32e9..ce3b61f 100644 --- a/lib/smql_to_ar.rb +++ b/lib/smql_to_ar.rb @@ -139,12 +139,11 @@ class SmqlToAR def == other other = Col.new other - @col = other.col && @as == other.col + @col == other.col && @as == other.as end def === other - other = Col.new other - @col == other.col + @col == Col.new( other).col end end From 493556fda2c99f9de3e8d4171d9e0273df407313 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Tue, 29 Nov 2011 12:01:38 +0100 Subject: [PATCH 20/37] Column::Col is comparable --- lib/smql_to_ar.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/smql_to_ar.rb b/lib/smql_to_ar.rb index ce3b61f..19ab574 100644 --- a/lib/smql_to_ar.rb +++ b/lib/smql_to_ar.rb @@ -120,6 +120,7 @@ class SmqlToAR attr_accessor :model class Col + include Comparable attr_accessor :col, :as def initialize col, as = nil if col.kind_of? Col From 2b3922e158a55a086fb32a53cbb3f33462a9139f Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Tue, 29 Nov 2011 12:02:54 +0100 Subject: [PATCH 21/37] v0.0.4.6 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 3c8f915..34d7ca5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.4.5 +0.0.4.6 From edcc68a4ed6240039ae084ae94cb3cd6ba2169cc Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Wed, 30 Nov 2011 13:52:19 +0100 Subject: [PATCH 22/37] dependency to methodphitamine removed. --- Rakefile | 1 - lib/smql_to_ar.rb | 4 ++-- lib/smql_to_ar/condition_types.rb | 13 ++++++++----- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Rakefile b/Rakefile index c9ac2a2..4245ce4 100644 --- a/Rakefile +++ b/Rakefile @@ -15,7 +15,6 @@ begin gem.add_dependency 'activerecord' gem.add_dependency 'activesupport' gem.add_dependency 'json' - gem.add_dependency 'methodphitamine' end Jeweler::GemcutterTasks.new rescue LoadError diff --git a/lib/smql_to_ar.rb b/lib/smql_to_ar.rb index 19ab574..d9404b2 100644 --- a/lib/smql_to_ar.rb +++ b/lib/smql_to_ar.rb @@ -152,10 +152,10 @@ class SmqlToAR @model = model @last_model = nil *@path, @col = *Array.wrap( col). - collect( &it.to_s.split( /[.\/]/)). + collect {|x| x.to_s.split /[.\/]/ }. flatten. collect( &Col.method( :new)). - reject( &it===:self) + reject {|x| x === :self } end def last_model diff --git a/lib/smql_to_ar/condition_types.rb b/lib/smql_to_ar/condition_types.rb index 639c9bf..cf47ccd 100644 --- a/lib/smql_to_ar/condition_types.rb +++ b/lib/smql_to_ar/condition_types.rb @@ -100,7 +100,7 @@ class SmqlToAR # Passt das Object, die Klasse instanzieren. def try_parse model, cols, op, val #p :class => self, :self => name, :try_parse => op, :cols => cols, :with => self::Operator, :value => val, :expected => self::Expected, :model => model.name - new model, cols, val if self::Operator === op and self::Expected.any?( &it === val) + new model, cols, val if self::Operator === op and self::Expected.any? {|x| x === val} end def inspect @@ -225,7 +225,7 @@ class SmqlToAR # { 'articles=>' => [ { id: 1 }, { id: 2 } ] } class EqualJoin Date: Wed, 30 Nov 2011 13:55:34 +0100 Subject: [PATCH 23/37] v0.0.4.7 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 34d7ca5..3e2497e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.4.6 +0.0.4.7 From 3d08496ec4c76d264d0dcc8db2d18f164d484dc4 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Wed, 30 Nov 2011 16:20:56 +0100 Subject: [PATCH 24/37] ( ( ) ( ) ) --- lib/smql_to_ar/query_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/smql_to_ar/query_builder.rb b/lib/smql_to_ar/query_builder.rb index ded1481..7e88255 100644 --- a/lib/smql_to_ar/query_builder.rb +++ b/lib/smql_to_ar/query_builder.rb @@ -244,7 +244,7 @@ class SmqlToAR def default() SmqlToAR::And end def default_new( parent) default.new self, parent, false end def collect_build_where - collect {|x| x.respond_to?( :build_where) ? x.build_where : x.to_s } + collect {|x| "( #{x.respond_to?( :build_where) ? x.build_where : x.to_s } )" } end end From 3ba285548c7627671c2e42744103ad839800f1b8 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Mon, 5 Dec 2011 12:39:41 +0100 Subject: [PATCH 25/37] patch fix --- lib/smql_to_ar/query_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/smql_to_ar/query_builder.rb b/lib/smql_to_ar/query_builder.rb index 7e88255..1299b4d 100644 --- a/lib/smql_to_ar/query_builder.rb +++ b/lib/smql_to_ar/query_builder.rb @@ -124,7 +124,7 @@ class SmqlToAR when :belongs_to @_joins += build_join query, pretable, t, refl.primary_key_name, premodel.primary_key when :has_and_belongs_to_many - jointable = [','] + table + jointable = [Column::Col.new('')] + table @_joins += build_join refl.options[:join_table], pretable, @table_alias[jointable], premodel.primary_key, refl.primary_key_name @_joins += build_join query, jointable, t, refl.association_foreign_key, refl.association_primary_key else raise BuilderError, "Unkown reflection macro: #{refl.macro.inspect}" From f6b957907386d59353a8aaf927bd039d33a55a50 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Thu, 12 Jan 2012 16:54:51 +0100 Subject: [PATCH 26/37] over-datanamelen-bug fixed --- lib/smql_to_ar/query_builder.rb | 50 ++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/lib/smql_to_ar/query_builder.rb b/lib/smql_to_ar/query_builder.rb index 1299b4d..81a3ca9 100644 --- a/lib/smql_to_ar/query_builder.rb +++ b/lib/smql_to_ar/query_builder.rb @@ -32,13 +32,36 @@ class SmqlToAR end class Aliases < Hash - def self.new prefix, *a, &e - e ||= lambda do |h, k| - j = Array.wrap( k).compact - h[k] = h.key?(j) ? h[j] : "#{prefix},#{j.collect( &:to_alias).join( ',')}" - end + attr_accessor :counter, :prefix + + def initialize prefix, *a, &e + @counter, @prefix = 0, prefix || 'smql' super *a, &e end + + def format name + pre, suf = name.split( ',', 2) + return name unless suf + pre += ",#{@counter += 1}," + l = 60-pre.length + n = suf[(suf.length<=l ? 0 : -l)..-1] + n == suf ? pre+n : "#{pre},,,#{n}" + end + + def name n + n.collect( &:to_alias).join ',' + end + + def [] k + n = name k + v = super n + v = self[k] = format( "#{prefix},#{n}") unless v + v + end + + def []= k, v + super name( k), v + end end attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins, :prefix, :_vid @@ -48,7 +71,7 @@ class SmqlToAR @prefix = "smql" @logger = SmqlToAR.logger @table_alias = Aliases.new @prefix - @_vid, @_where, @_wobs, @model, @quoter = 0, SmqlToAR::And[], {}, model, model.connection + @_vid, @_where, @_wobs, @model, @quote = 0, SmqlToAR::And[], {}, model, model.connection @base_table = [Column::Col.new( model.table_name)] @table_alias[ @base_table] = @base_table.first t = quote_table_name @base_table.first.col @@ -77,7 +100,7 @@ class SmqlToAR end def quote_column_name name - @quoter.quote_column_name( name).gsub /"\."/, ',' + @quote.quote_column_name( name).gsub /"\."/, ',' end def quote_table_name name @@ -85,7 +108,7 @@ class SmqlToAR when Array, Column::Col then @table_alias[Array.wrap name] else name.to_s end - @quoter.quote_table_name( name).gsub /"\."/, ',' + @quote.quote_table_name name end def column table, name @@ -93,7 +116,7 @@ class SmqlToAR end def build_join orig, pretable, table, prekey, key - " LEFT JOIN #{orig} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} " + "\tLEFT JOIN #{orig} AS #{quote_table_name table}\n\tON #{column pretable, prekey} = #{column table, key}\n" end def sub_joins table, col, model, query @@ -104,7 +127,8 @@ class SmqlToAR def join_ table, model, query, pretable = nil pretable ||= table[0...-1] @table_model[ table] = model - premodel = @table_model[ pretable] + @table_model.rehash + premodel = @table_model.find {|k,v| pretable == k }[1] t = @table_alias[ table] pt = quote_table_name table[ 0...-1] refl = premodel.reflections[table.last.to_sym] @@ -124,7 +148,7 @@ class SmqlToAR when :belongs_to @_joins += build_join query, pretable, t, refl.primary_key_name, premodel.primary_key when :has_and_belongs_to_many - jointable = [Column::Col.new('')] + table + jointable = [','] + table @_joins += build_join refl.options[:join_table], pretable, @table_alias[jointable], premodel.primary_key, refl.primary_key_name @_joins += build_join query, jointable, t, refl.association_foreign_key, refl.association_primary_key else raise BuilderError, "Unkown reflection macro: #{refl.macro.inspect}" @@ -195,7 +219,7 @@ class SmqlToAR class SubBuilder < Array attr_reader :parent, :_where - delegate :wobs, :joins, :includes, :sub_joins, :vid, :quote_column_name, :quoter, :quote_table_name, :column, :to => :parent + delegate :wobs, :joins, :includes, :sub_joins, :vid, :quote_column_name, :quote, :quote_table_name, :column, :to => :parent def initialize parent, tmp = false @parent = parent @@ -244,7 +268,7 @@ class SmqlToAR def default() SmqlToAR::And end def default_new( parent) default.new self, parent, false end def collect_build_where - collect {|x| "( #{x.respond_to?( :build_where) ? x.build_where : x.to_s } )" } + collect {|x| x.respond_to?( :build_where) ? x.build_where : x.to_s } end end From a4a1d1b9e9ce22463d3be7cc19347dd288c044d2 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Thu, 12 Jan 2012 16:55:25 +0100 Subject: [PATCH 27/37] v0.0.4.8 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 3e2497e..706161a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.4.7 +0.0.4.8 From 6f8569f80a6c7c15fcbc6141eccebea9685634a6 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Thu, 12 Jan 2012 17:11:55 +0100 Subject: [PATCH 28/37] =?UTF-8?q?OVERLAPS-Operator=20<=3D>=20bzw.=20=20?= =?UTF-8?q?implementiert.=20=20SmqlToAR.reload=5Flibrary=20l=C3=B6scht=20z?= =?UTF-8?q?uerst=20SmqlToAR,=20l=C3=A4d=20dann=20erst=20neu.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/smql_to_ar.rb | 5 +-- lib/smql_to_ar/condition_types.rb | 64 +++++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/lib/smql_to_ar.rb b/lib/smql_to_ar.rb index d9404b2..5071a31 100644 --- a/lib/smql_to_ar.rb +++ b/lib/smql_to_ar.rb @@ -222,10 +222,8 @@ class SmqlToAR SmqlToAR.logger = ::Rails.logger end end - else - require 'logger' - @@logger = Logger.new $stdout end + @@logger = ::Rails.logger || begin require 'logger'; Logger.new( $stdout) end class <. +# TODO: +# * Array als Typ ist zu allgemein. Allgemeine Lösung gesucht, um die Typen im Array angeben zu können. + class SmqlToAR ############################################################################# # Alle Subklassen (qualitativ: ConditionTypes::*), die als Superklasse Condition haben, @@ -104,7 +107,7 @@ class SmqlToAR end def inspect - "#{self.name}(:operator=>#{self::Operator.inspect}, :expected=>#{self::Expected.inspect}, :where=>#{self::Where.inspect})" + "#{self.name}( :operator=>#{self::Operator.inspect}, :expected=>#{self::Expected.inspect}, :where=>#{self::Where.inspect})" end end @@ -175,11 +178,11 @@ class SmqlToAR class NotInRange < Condition Operator = '!..' - Where = "%s NOT BETWEEN %s AND %s" + Where = '%s NOT BETWEEN %s AND %s' Expected = [Range, lambda {|val| Array === val && 2 == val.length } ] def initialize model, cols, val - if Array === val && 2 == val.length + if Array === val f, l = val f, l = Time.parse(f), Time.parse(l) if f.kind_of? String val = f..l @@ -196,7 +199,45 @@ class SmqlToAR self end end - InRange = simple_condition NotInRange, '..', "%s BETWEEN %s AND %s" + InRange = simple_condition NotInRange, '..', '%s BETWEEN %s AND %s' + + class NotOverlaps < Condition + Operator, Where = '', 'NOT (%s, %s) OVERLAPS (%s, %s)' + Expected = [Range, lambda {|val| + Array === val && 2 == val.length && + [Time, Date, String].any? {|v|v===val[0]} && + [Numeric, String].any? {|v|v===val[1]} + }] + + def initialize model, cols, val + if Array === val + f = Time.parse( val[0]).localtime + l = val[1] + l = case l + when String then Time.parse( l).localtime + when Numeric then f+l + else raise ArgumentError, "Unexpected type for end-value #{l.inspect}" + end + f += f.utc_offset + l += l.utc_offset + val = f.utc..l.utc + end + super model, cols, val + end + + def build builder, table + builder.wobs (v1 = builder.vid).to_sym => @value.begin, (v2 = builder.vid).to_sym => @value.end + v1 = "TIMESTAMP #{v1}" + v2 = "TIMESTAMP #{v2}" + @cols.each do |col| + col.joins builder, table + end.each_slice 2 do |f,s| + builder.where self.class::Where % [ + builder.column( table+f.path, f.col), builder.column( table+s.path, s.col), v1, v2] + end + end + end + Overlaps = simple_condition NotOverlaps, '<=>', '(%s, %s) OVERLAPS (%s, %s)' class NotIn < Condition Operator = '!|=' @@ -219,6 +260,13 @@ class SmqlToAR NotEqual2 = simple_condition Condition, '<>', "%s <> %s", [Array, String, Numeric] GreaterThanOrEqual = simple_condition Condition, '>=', "%s >= %s", [Array, Numeric] LesserThanOrEqual = simple_condition Condition, '<=', "%s <= %s", [Array, Numeric] + class StringTimeGreaterThanOrEqual < Condition + Operator, Where, Expected = '>=', '%s >= %s', [Time, Date, String] + def initialize model, cols, val + super model, cols, Time.parse( val.to_s) + end + end + StringTimeLesserThanOrEqual = simple_condition StringTimeGreaterThanOrEqual, '<=', "%s <= %s" # Examples: # { 'articles=>' => { id: 1 } } @@ -303,10 +351,12 @@ class SmqlToAR end =end - Equal = simple_condition Condition, '=', "%s = %s", [Array, String, Numeric] - Equal2 = simple_condition Equal, '', "%s = %s", [String, Numeric] + Equal = simple_condition Condition, '=', "%s = %s", [Array, String, Numeric, Date, Time] + Equal2 = simple_condition Equal, '', "%s = %s", [String, Numeric, Date, Time] GreaterThan = simple_condition Condition, '>', "%s > %s", [Array, Numeric] + StringTimeGreaterThan = simple_condition StringTimeGreaterThanOrEqual, '>', "%s > %s" LesserThan = simple_condition Condition, '<', "%s < %s", [Array, Numeric] + StringTimeLesserThan = simple_condition StringTimeGreaterThanOrEqual, '<', "%s < %s" NotIlike = simple_condition Condition, '!~', "%s NOT ILIKE %s", [Array, String] Ilike = simple_condition Condition, '~', "%s ILIKE %s", [Array, String] Exists = simple_condition Condition, '', '%s IS NOT NULL', [TrueClass] @@ -352,7 +402,7 @@ class SmqlToAR end def inspect - "#{self.name}(:name=>#{self::Name}, :expected=>#{self::Expected})" + "#{self.name}( :name=>#{self::Name}, :expected=>#{self::Expected})" end end From 40deac93bf78fb520ba836bbb075c72e5b1f2d22 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Thu, 12 Jan 2012 17:12:22 +0100 Subject: [PATCH 29/37] v0.0.5 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 706161a..bbdeab6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.4.8 +0.0.5 From 27639e24b72406db31c35ad1e2fdde28e67f0883 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Mon, 16 Jan 2012 12:52:01 +0100 Subject: [PATCH 30/37] #join: ",".to_alias OPS! --- lib/smql_to_ar/query_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/smql_to_ar/query_builder.rb b/lib/smql_to_ar/query_builder.rb index 81a3ca9..af46ee5 100644 --- a/lib/smql_to_ar/query_builder.rb +++ b/lib/smql_to_ar/query_builder.rb @@ -148,7 +148,7 @@ class SmqlToAR when :belongs_to @_joins += build_join query, pretable, t, refl.primary_key_name, premodel.primary_key when :has_and_belongs_to_many - jointable = [','] + table + jointable = [Column::Col.new(',')] + table @_joins += build_join refl.options[:join_table], pretable, @table_alias[jointable], premodel.primary_key, refl.primary_key_name @_joins += build_join query, jointable, t, refl.association_foreign_key, refl.association_primary_key else raise BuilderError, "Unkown reflection macro: #{refl.macro.inspect}" From fbf5a7019c326235feb46bbbec2572bc49458d44 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Mon, 16 Jan 2012 12:52:28 +0100 Subject: [PATCH 31/37] v0.0.5.1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index bbdeab6..442b113 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.5 +0.0.5.1 From ce81d21bd868b6cc19242cbf350eb0aecae5c17e Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Tue, 17 Jan 2012 10:52:31 +0100 Subject: [PATCH 32/37] v0.0.5.2. build-methods renamed to an underscored class-name with suffix _build and aliases build to these methods created. equal_join_build fixed; it never use col.joins.each. #foreigh_key --- VERSION | 2 +- lib/smql_to_ar/condition_types.rb | 38 +++++++++++++++++++------------ lib/smql_to_ar/query_builder.rb | 16 ++++++------- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/VERSION b/VERSION index 442b113..e338f86 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.5.1 +0.0.5.2 diff --git a/lib/smql_to_ar/condition_types.rb b/lib/smql_to_ar/condition_types.rb index 013ce6b..3691b40 100644 --- a/lib/smql_to_ar/condition_types.rb +++ b/lib/smql_to_ar/condition_types.rb @@ -153,7 +153,7 @@ class SmqlToAR # 2) {"givenname=", ["Peter", "Hans"]} #=> ( givenname = 'Peter' OR givenname = 'Hans' ) # 3) {"givenname|surname=", ["Peter", "Mueller"]} # #=> ( givenname = 'Peter' OR surname = 'Peter' ) AND ( givenname = 'Mueller' OR surname = 'Mueller' ) - def build builder, table + def condition_build builder, table values = Hash[ @value.collect {|value| [ builder.vid, value ] } ] values.each {|k, v| builder.wobs k.to_sym => v } if 1 == @cols.length @@ -174,6 +174,7 @@ class SmqlToAR end self end + alias build condition_build end class NotInRange < Condition @@ -190,7 +191,7 @@ class SmqlToAR super model, cols, val end - def build builder, table + def not_in_range_build builder, table builder.wobs (v1 = builder.vid).to_sym => @value.begin, (v2 = builder.vid).to_sym => @value.end @cols.each do |col| col.joins builder, table @@ -198,11 +199,12 @@ class SmqlToAR end self end + alias build not_in_range_build end InRange = simple_condition NotInRange, '..', '%s BETWEEN %s AND %s' - class NotOverlaps < Condition - Operator, Where = '', 'NOT (%s, %s) OVERLAPS (%s, %s)' + class Overlaps < Condition + Operator, Where = '<=>', '(%s, %s) OVERLAPS (%s, %s)' Expected = [Range, lambda {|val| Array === val && 2 == val.length && [Time, Date, String].any? {|v|v===val[0]} && @@ -225,7 +227,7 @@ class SmqlToAR super model, cols, val end - def build builder, table + def overlaps_build builder, table builder.wobs (v1 = builder.vid).to_sym => @value.begin, (v2 = builder.vid).to_sym => @value.end v1 = "TIMESTAMP #{v1}" v2 = "TIMESTAMP #{v2}" @@ -236,15 +238,16 @@ class SmqlToAR builder.column( table+f.path, f.col), builder.column( table+s.path, s.col), v1, v2] end end + alias build overlaps_build end - Overlaps = simple_condition NotOverlaps, '<=>', '(%s, %s) OVERLAPS (%s, %s)' + NotOverlaps = simple_condition Overlaps, '<=>', 'NOT (%s, %s) OVERLAPS (%s, %s)' class NotIn < Condition Operator = '!|=' Where = "%s NOT IN (%s)" Expected = [Array] - def build builder, table + def not_in_build builder, table builder.wobs (v = builder.vid).to_sym => @value @cols.each do |col| col.joins builder, table @@ -252,6 +255,7 @@ class SmqlToAR end self end + alias build not_in_build end In = simple_condition NotIn, '|=', '%s IN (%s)', [Array] @@ -290,7 +294,7 @@ class SmqlToAR raise_unless col.relation, NonExistingRelationError.new( %w[Relation], col) end - def build builder, table + def equal_join_build builder, table if 2 < @cols.first.second.length b2, b3 = And, Or else @@ -300,7 +304,7 @@ class SmqlToAR @cols.each do |col, sub| model, *sub = sub t = table + col.path + [col.col] - col.joins.each {|j, m| builder.joins table+j, m } + col.joins builder, table builder.joins t, model b4 = b3.new( b2) sub.each do |i| @@ -310,6 +314,7 @@ class SmqlToAR end self end + alias build equal_join_build end # Takes to Queries. @@ -339,7 +344,7 @@ class SmqlToAR raise_unless col.child?, ConColumnError.new( [:Column], col) end - def build builder, table + def sub_equal_join_build builder, table @cols.each do |col, sub| t = table+col.to_a builder.sub_joins t, col, *sub[0..1] @@ -348,6 +353,7 @@ class SmqlToAR end self end + alias build sub_equal_join_build end =end @@ -372,7 +378,7 @@ class SmqlToAR raise_unless col.exist_in? || SmqlToAR.model_of( col.last_model, col.col), NonExistingSelectableError.new( col) end - def build builder, table + def select_build builder, table @cols.each do |col| if col.exist_in? col.joins builder, table @@ -384,6 +390,7 @@ class SmqlToAR end self end + alias build select_build end class Functions < Condition @@ -432,7 +439,7 @@ class SmqlToAR super model, func, args end - def build builder, table + def order_build builder, table return if @args.blank? @args.each do |o| col, o = o @@ -442,26 +449,29 @@ class SmqlToAR builder.order t, col.col, o end end + alias build order_build end class Limit < Function Name = :limit Expected = [Fixnum] - def build builder, table + def limit_build builder, table raise_unless 1 == table.length, RootOnlyFunctionError.new( table) builder.limit = Array.wrap(@args).first.to_i end + alias build limit_build end class Offset < Function Name = :offset Expected = [Fixnum] - def build builder, table + def offset_build builder, table raise_unless 1 == table.length, RootOnlyFunctionError.new( table) builder.offset = Array.wrap(@args).first.to_i end + alias build offset_build end def self.new model, col, val diff --git a/lib/smql_to_ar/query_builder.rb b/lib/smql_to_ar/query_builder.rb index af46ee5..0781dc7 100644 --- a/lib/smql_to_ar/query_builder.rb +++ b/lib/smql_to_ar/query_builder.rb @@ -135,25 +135,25 @@ class SmqlToAR case refl when ActiveRecord::Reflection::ThroughReflection through = refl.through_reflection - throughtable = table[0...-1]+[Column::Col.new( through.name, table.last.as)] - srctable = throughtable+[Column::Col.new( refl.source_reflection.name, table.last.as)] + through_table = table[0...-1]+[Column::Col.new( through.name, table.last.as)] + srctable = through_table+[Column::Col.new( refl.source_reflection.name, table.last.as)] @table_model[ srctable] = model @table_alias[ table] = @table_alias[ srctable] - join_ throughtable, through.klass, quote_table_name( through.table_name) - join_ srctable, refl.klass, query, throughtable + join_ through_table, through.klass, quote_table_name( through.table_name) + join_ srctable, refl.klass, query, through_table when ActiveRecord::Reflection::AssociationReflection case refl.macro when :has_many, :has_one - @_joins += build_join query, pretable, t, premodel.primary_key, refl.primary_key_name + @_joins += build_join query, pretable, t, premodel.primary_key, refl.foreign_key when :belongs_to - @_joins += build_join query, pretable, t, refl.primary_key_name, premodel.primary_key + @_joins += build_join query, pretable, t, refl.foreign_key, premodel.primary_key when :has_and_belongs_to_many jointable = [Column::Col.new(',')] + table - @_joins += build_join refl.options[:join_table], pretable, @table_alias[jointable], premodel.primary_key, refl.primary_key_name + @_joins += build_join refl.options[:join_table], pretable, @table_alias[ jointable], premodel.primary_key, refl.foreign_key @_joins += build_join query, jointable, t, refl.association_foreign_key, refl.association_primary_key else raise BuilderError, "Unkown reflection macro: #{refl.macro.inspect}" end - else raise BuilderError, "Unkown reflection type: #{refl.class.name}" + else raise BuilderError, "Unkown reflection type: #{refl.class.name} #{refl.macro.inspect}" end self end From 08d161098714e15c0fd6715b23ccba6648263d4c Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Wed, 18 Jan 2012 13:34:26 +0100 Subject: [PATCH 33/37] optimizer optimized (AND[Or["abc"]] -> "abc"). ()-bugfix in where-clause. --- VERSION | 2 +- lib/smql_to_ar/query_builder.rb | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/VERSION b/VERSION index e338f86..78a0339 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.5.2 +0.0.5.3 diff --git a/lib/smql_to_ar/query_builder.rb b/lib/smql_to_ar/query_builder.rb index 0781dc7..4e8ad3c 100644 --- a/lib/smql_to_ar/query_builder.rb +++ b/lib/smql_to_ar/query_builder.rb @@ -248,12 +248,14 @@ class SmqlToAR def optimize! ext = [] collect! do |sub| - sub = sub.optimize! if sub.kind_of? Array + sub = sub.optimize! if sub.kind_of? SubBuilder if self.class == sub.class ext.push *sub nil elsif sub.blank? nil + elsif 1 == sub.size + sub.first else sub end @@ -267,21 +269,22 @@ class SmqlToAR end def default() SmqlToAR::And end def default_new( parent) default.new self, parent, false end - def collect_build_where - collect {|x| x.respond_to?( :build_where) ? x.build_where : x.to_s } + def collect_build_where indent = nil + indent = (indent||0) + 1 + collect {|x| "(#{x.respond_to?( :build_where) ? x.build_where( indent) : x.to_s})" } end end class And < SubBuilder def default; SmqlToAR::Or; end - def build_where - collect_build_where.join ' AND ' + def build_where indent = nil + collect_build_where( indent).join " AND\n"+"\t"*(indent||0) end end class Or < SubBuilder - def build_where - collect_build_where.join ' OR ' + def build_where indent = nil + collect_build_where( indent).join " OR\n"+"\t"*(indent||0) end end end From 7229fd185c0651867b38942098c4e73f644bc7bc Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Thu, 9 Feb 2012 13:31:24 +0100 Subject: [PATCH 34/37] overlaps: key-pairs will be ored --- lib/smql_to_ar/condition_types.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/smql_to_ar/condition_types.rb b/lib/smql_to_ar/condition_types.rb index 3691b40..548cd89 100644 --- a/lib/smql_to_ar/condition_types.rb +++ b/lib/smql_to_ar/condition_types.rb @@ -203,6 +203,7 @@ class SmqlToAR end InRange = simple_condition NotInRange, '..', '%s BETWEEN %s AND %s' + # Every key-pair will be ORed. No multiple values possible. class Overlaps < Condition Operator, Where = '<=>', '(%s, %s) OVERLAPS (%s, %s)' Expected = [Range, lambda {|val| @@ -228,6 +229,7 @@ class SmqlToAR end def overlaps_build builder, table + builder = Or.new builder builder.wobs (v1 = builder.vid).to_sym => @value.begin, (v2 = builder.vid).to_sym => @value.end v1 = "TIMESTAMP #{v1}" v2 = "TIMESTAMP #{v2}" From 3d922c5e473333a6a62ae0a5a167ce520de12b67 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Thu, 9 Feb 2012 14:10:58 +0100 Subject: [PATCH 35/37] v0.0.6 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 78a0339..1750564 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.5.3 +0.0.6 From 7f43e9fb86f411f3bdb5d1a57b327675044d080d Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Tue, 3 Jul 2012 16:28:27 +0200 Subject: [PATCH 36/37] not compatible to ruby1.8 now! new: ActiveRecordExtensions --- lib/smql_to_ar.rb | 48 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/lib/smql_to_ar.rb b/lib/smql_to_ar.rb index 5071a31..702c554 100644 --- a/lib/smql_to_ar.rb +++ b/lib/smql_to_ar.rb @@ -43,9 +43,9 @@ class SmqlToAR # Die eigentliche Exception wird in @data[:exception] hinterlegt. class SubSMQLError < SMQLError def initialize query, model, exception - ex = {:class => exception.class, :message => exception.message} + ex = {class: exception.class, message: exception.message} ex[:data] = exception.data if exception.respond_to? :data - super :query => query, :model => model.to_s, :exception => ex + super query: query, model: model.to_s, exception: ex set_backtrace exception.backtrace end end @@ -55,49 +55,49 @@ class SmqlToAR # Malformed ColOp class UnexpectedColOpError < ParseError def initialize model, colop, val - super :got => colop, :val => val, :model => model.to_s + super got: colop, val: val, model: model.to_s end end class UnexpectedError < ParseError def initialize model, colop, val - super :got => {colop => val}, :model => model.to_s + super got: {colop => val}, model: model.to_s end end class NonExistingSelectableError < SMQLError def initialize got - super :got => got + super got: got end end class NonExistingColumnError < SMQLError def initialize expected, got - super :expected => expected, :got => got + super expected: expected, got: got end end class NonExistingRelationError < SMQLError def initialize expected, got - super :expected => expected, :got => got + super expected: expected, got: got end end class ProtectedColumnError < SMQLError def initialize protected_column - super :protected_column => protected_column + super protected_column: protected_column end end class RootOnlyFunctionError < SMQLError def initialize path - super :path => path + super path: path end end class ConColumnError < SMQLError def initialize expected, got - super :expected => expected, :got => got + super expected: expected, got: got end end @@ -243,8 +243,8 @@ class SmqlToAR refls = model.respond_to?( :reflections) && model.reflections refls && refls.each do |name, refl| r[model.name][name] = case refl - when ActiveRecord::Reflection::ThroughReflection then {:macro => refl.macro, :model => refl.klass.name, :through => refl.through_reflection.name} - when ActiveRecord::Reflection::AssociationReflection then {:macro => refl.macro, :model => refl.klass.name} + when ActiveRecord::Reflection::ThroughReflection then {macro: refl.macro, model: refl.klass.name, through: refl.through_reflection.name} + when ActiveRecord::Reflection::AssociationReflection then {macro: refl.macro, model: refl.klass.name} else raise "Ups: #{refl.class}" end models.push refl.klass unless r.keys.include? refl.klass.name @@ -293,8 +293,26 @@ class SmqlToAR lib_dir = File.dirname __FILE__ fj = lambda {|*a| File.join lib_dir, *a } Object.send :remove_const, :SmqlToAR - load fj.call( 'smql_to_ar.rb') - load fj.call( 'smql_to_ar', 'condition_types.rb') - load fj.call( 'smql_to_ar', 'query_builder.rb') + load fj[ 'smql_to_ar.rb'] + load fj[ 'smql_to_ar', 'condition_types.rb'] + load fj[ 'smql_to_ar', 'query_builder.rb'] + end + + module ActiveRecordExtensions + def self.included base + base.extend ClassMethods + end + + module ClassMethods + def smql *params + SmqlToAR.to_ar self, *params + end + + def smql_protected + [] + end + end end end + +ActiveRecord::Base.send :include, SmqlToAR::ActiveRecordExtensions From a4ce8a1e6b9db2438c3f930f3f46b2d9cca40b28 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Tue, 3 Jul 2012 16:28:46 +0200 Subject: [PATCH 37/37] v0.0.7 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 1750564..5a5831a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.6 +0.0.7