lib/activefacts/generate/sql/server.rb in activefacts-0.7.3 vs lib/activefacts/generate/sql/server.rb in activefacts-0.8.5

- old
+ new

@@ -13,11 +13,11 @@ # Generate SQL for SQL Server for an ActiveFacts vocabulary. # Invoke as # afgen --sql/server[=options] <file>.cql # Options are comma or space separated: # * delay_fks Leave all foreign keys until the end, not just those that contain forward-references - # * norma Translate datatypes from NORMA to SQL Server + # * norma Translate valuetypes from NORMA to SQL Server class SERVER private include Persistence ColumnNameMax = 40 @@ -47,10 +47,11 @@ def initialize(vocabulary, *options) @vocabulary = vocabulary @vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary @delay_fks = options.include? "delay_fks" @norma = options.include? "norma" + @underscore = options.include?("underscore") ? "_" : "" end def puts s @out.puts s end @@ -100,11 +101,11 @@ when "Money"; "decimal" when "PictureRawData"; "image" when "VariableLengthRawData"; "varbinary" when "BIT"; "bit" - else raise "SQL type unknown for NORMA type #{type}" + else type # raise "SQL type unknown for NORMA type #{type}" end [sql_type, length] end public @@ -114,11 +115,11 @@ tables_emitted = {} delayed_foreign_keys = [] @vocabulary.tables.each do |table| - puts "CREATE TABLE #{escape table.name} (" + puts "CREATE TABLE #{escape table.name(@underscore)} (" pk = table.identifier_columns identity_column = pk[0] if pk.size == 1 && pk[0].is_auto_assigned fk_refs = table.references_from.select{|ref| ref.is_simple_reference } @@ -126,12 +127,12 @@ column.references[0].is_simple_reference end # We sort the columns here, not in the persistence layer, because it affects # the ordering of columns in an index :-(. - columns = table.columns.sort_by { |column| column.name(nil) }.map do |column| - name = escape column.name("") + columns = table.columns.sort_by { |column| column.name(@underscore) }.map do |column| + name = escape column.name(@underscore) padding = " "*(name.size >= ColumnNameMax ? 1 : ColumnNameMax-name.size) type, params, restrictions = column.type restrictions = [] if (fk_columns.include?(column)) # Don't enforce VT restrictions on FK columns length = params[:length] length &&= length.to_i @@ -151,54 +152,54 @@ comment = column.comment [ "-- #{comment}", "#{name}#{padding}#{sql_type}#{identity} #{null}#{check}" ] end.flatten pk_def = (pk.detect{|column| !column.is_mandatory} ? "UNIQUE(" : "PRIMARY KEY(") + - pk.map{|column| escape column.name("")}*", " + + pk.map{|column| escape column.name(@underscore)}*", " + ")" inline_fks = [] table.foreign_keys.each do |fk| fk_text = "FOREIGN KEY (" + - fk.from_columns.map{|column| column.name}*", " + - ") REFERENCES #{escape fk.to.name} (" + - fk.to_columns.map{|column| column.name}*", " + + fk.from_columns.map{|column| column.name(@underscore)}*", " + + ") REFERENCES #{escape fk.to.name(@underscore)} (" + + fk.to_columns.map{|column| column.name(@underscore)}*", " + ")" if !@delay_fks and # We don't want to delay all Fks (tables_emitted[fk.to] or # The target table has been emitted fk.to == table && !fk.to_columns.detect{|column| !column.is_mandatory}) # The reference columns already have the required indexes inline_fks << fk_text else - delayed_foreign_keys << ("ALTER TABLE #{escape fk.from.name}\n\tADD " + fk_text) + delayed_foreign_keys << ("ALTER TABLE #{escape fk.from.name(@underscore)}\n\tADD " + fk_text) end end indices = table.indices inline_indices = [] delayed_indices = [] indices.each do |index| next if index.over == table && index.is_primary # Already did the primary keys - abbreviated_column_names = index.abbreviated_column_names*"" - column_names = index.column_names + abbreviated_column_names = index.abbreviated_column_names(@underscore)*"" + column_names = index.column_names(@underscore) column_name_list = column_names.map{|n| escape(n)}*", " if index.columns.all?{|column| column.is_mandatory} inline_indices << "UNIQUE(#{column_name_list})" else view_name = escape "#{index.view_name}_#{abbreviated_column_names}" delayed_indices << %Q{CREATE VIEW dbo.#{view_name} (#{column_name_list}) WITH SCHEMABINDING AS -\tSELECT #{column_name_list} FROM dbo.#{escape index.on.name} +\tSELECT #{column_name_list} FROM dbo.#{escape index.on.name(@underscore)} \tWHERE\t#{ index.columns. select{|column| !column.is_mandatory }. map{|column| - escape(column.name) + " IS NOT NULL" + escape(column.name(@underscore)) + " IS NOT NULL" }*"\n\t AND\t" } GO -CREATE UNIQUE CLUSTERED INDEX #{escape index.name} ON dbo.#{view_name}(#{index.columns.map{|column| column.name}*", "}) +CREATE UNIQUE CLUSTERED INDEX #{escape index.name} ON dbo.#{view_name}(#{index.columns.map{|column| column.name(@underscore)}*", "}) } end end tables_emitted[table] = true @@ -214,27 +215,31 @@ go fk end end private + def sql_value(value) + value.is_a_string ? sql_string(value.literal) : value.literal + end + + def sql_string(str) + "'" + str.gsub(/'/,"''") + "'" + end + def check_clause(column_name, restrictions) return "" if restrictions.empty? # REVISIT: Merge all restrictions (later; now just use the first) " CHECK(" + - restrictions[0].all_allowed_range.sort_by do |ar| - # Put the allowed ranges into a defined order: - ((min = ar.value_range.minimum_bound) && min.value) || - ((max = ar.value_range.maximum_bound) && max.value) - end.map do |ar| + restrictions[0].all_allowed_range_sorted.map do |ar| vr = ar.value_range min = vr.minimum_bound max = vr.maximum_bound - if (min && max && max.value == min.value) - "#{column_name} = #{min.value}" + if (min && max && max.value.literal == min.value.literal) + "#{column_name} = #{sql_value(min.value)}" else inequalities = [ - min && "#{column_name} >#{min.is_inclusive ? "=" : ""} #{min.value}", - max && "#{column_name} <#{max.is_inclusive ? "=" : ""} #{max.value}" + min && "#{column_name} >#{min.is_inclusive ? "=" : ""} #{sql_value(min.value)}", + max && "#{column_name} <#{max.is_inclusive ? "=" : ""} #{sql_value(max.value)}" ].compact inequalities.size > 1 ? "(" + inequalities*" AND " + ")" : inequalities[0] end end*" OR " + ")"