# SmqlToAR - Base library: Converts SMQL to ActiveRecord
# Copyright (C) 2011 Denis Knauf
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# 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
class SMQLError < Exception
attr_reader :data
def initialize data
@data = data
super data.inspect
end
end
# Ein Fehler ist in einem Subquery aufgetreten.
# Die eigentliche Exception wird in @data[:exception] hinterlegt.
class SubSMQLError < SMQLError
def initialize query, model, exception
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
set_backtrace exception.backtrace
end
end
class ParseError < SMQLError; end
# Malformed ColOp
class UnexpectedColOpError < ParseError
def initialize model, colop, val
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
end
end
class NonExistingSelectableError < SMQLError
def initialize got
super got: got
end
end
class NonExistingColumnError < SMQLError
def initialize expected, got
super expected: expected, got: got
end
end
class NonExistingRelationError < SMQLError
def initialize expected, got
super expected: expected, got: got
end
end
class ProtectedColumnError < SMQLError
def initialize protected_column
super protected_column: protected_column
end
end
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
#############################################################################
# Model der Relation `rel` von `model`
def self.model_of model, rel
r = model.reflections[ rel.to_sym].andand.klass
r.nil? && rel === :self ? model : r
end
# Eine Spalte in einer Tabelle, relativ zu `Column#model`.
# Kann auch einen Pfad `Column#path` haben, wobei `Column#col` dann
# eine Relation von dem Model der letzten Relation von `Column#path` ist.
class Column
include Enumerable
attr_reader :path, :col
attr_accessor :model
class Col
include Comparable
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.as
end
def === other
@col == Col.new( other).col
end
end
def initialize model, *col
@model = model
@last_model = nil
*@path, @col = *Array.wrap( col).
collect {|x| x.to_s.split /[.\/]/ }.
flatten.
collect( &Col.method( :new)).
reject {|x| x === :self }
end
def last_model
@last_model ||= each{}
end
def each
model = @model
@path.each do |rel|
unless rel === :self
model = SmqlToAR.model_of model, rel
return false unless model
yield rel, model
end
end
model
end
def exist_in?
model = last_model
return false unless model
model.column_names.include? @col.to_s
end
def protected?
model = @model
each do |rel, _model|
pr = Array.wrap model.respond_to?( :smql_protected) ? model.smql_protected : nil
pr.include? rel.to_s
model = _model
end
pr = Array.wrap model.respond_to?( :smql_protected) ? model.smql_protected : nil
pr.include? @col.to_s
end
def joins builder = nil, table = nil, &exe
pp = []
table = Array.wrap table
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 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() self.self? ? model : 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
attr_accessor :logger
if defined? Rails
class Railtie < ::Rails::Railtie
initializer "active_record.logger" do
SmqlToAR.logger = ::Rails.logger
end
end
end
@@logger = ::Rails.logger || begin require 'logger'; Logger.new( $stdout) end
class <