class Ferret::Table

Attributes

name[R]
primary_key[R]

FIXME: move to the section for data model

Public Class Methods

new(name) click to toggle source
Calls superclass method
# File lib/sql-ferret.rb, line 551
def initialize name
  raise 'type mismatch' unless name.is_a? String
  super()
  @name = name
  @fields = {} # keyed by forced-lowercase names
  return
end

Public Instance Methods

[](name) click to toggle source
# File lib/sql-ferret.rb, line 559
def [] name
  return @fields[name.downcase]
end
add_field(field) click to toggle source
[Table#add_field]

is how new [[Field]]:s get added to a

[Table]

as it gets parsed from a Ferret schema. Thus, we

check for field name duplication and primary key clashes here. This is also a convenient place to set up [[Table@primary_key]], too, as well as to check against a table having been declared with multiple primary keys.

# File lib/sql-ferret.rb, line 584
def add_field field
  raise 'type mismatch' unless field.is_a? Ferret::Field
  raise 'assertion failed' \
      unless field.table.object_id == self.object_id
  dname = field.name.downcase
  ugh? table: @name do
    ugh 'duplicate-field', field: field.name \
        if @fields.has_key? dname
    if field.primary_key? then
      if @primary_key then
        ugh 'primary-key-clash',
            key1: @primary_key.name,
            key2: field.name
      end
      @primary_key = field
    end
  end
  @fields[dname] = field
  return field
end
columns() click to toggle source
# File lib/sql-ferret.rb, line 567
def columns
  return @fields.values.select(&:column?)
end
empty?() click to toggle source
# File lib/sql-ferret.rb, line 563
def empty?
  return @fields.empty?
end
has_columns?() click to toggle source
# File lib/sql-ferret.rb, line 571
def has_columns?
  return @fields.values.any?(&:column?)
end
resolve_column_names(names) click to toggle source
# File lib/sql-ferret.rb, line 699
def resolve_column_names names
  results = []
  names.each do |fn|
    raise 'type mismatch' \
        unless fn.is_a? String
    field = @fields[fn.downcase]
    ugh 'unknown-field', field: fn,
            known_fields: @fields.values.map(&:name).
                join(', ') \
        unless field
    ugh 'not-a-column', field: field.name \
        unless field.column?
    ugh 'duplicate-field', field: field.name \
        if results.include? field
    results.push field
  end
  return results
end
sole_unique_column_among(column_names) click to toggle source

Given a list of column names, figure out which of them is the one and only unique (or primary key) field for this table. Ugh if any of them is not a field name; if any field is mentioned multiple times; if multiple [[unique]] fields are mentioned; or if no [[unique]] fields are mentioned.

# File lib/sql-ferret.rb, line 648
def sole_unique_column_among column_names
  ugh? table: @name do
    given_columns = resolve_column_names column_names
    unique_column = nil
    given_columns.each do |column|
      if column.unique? then
        if unique_column then
          ugh 'unique-column-conflict',
              field1: unique_column.name,
              field2: column.name
        end
        unique_column = column
      end
    end
    ugh 'no-unique-column-given',
            fields: given_columns.map(&:name).join(', '),
            known_unique_fields:
                @fields.values.select(&:unique?).
                    map(&:name).join(', ') \
        unless unique_column
    return unique_column
  end
end
sql_to_change(given_column_names) click to toggle source
# File lib/sql-ferret.rb, line 605
def sql_to_change given_column_names
  key_column = sole_unique_column_among given_column_names

  given_columns = resolve_column_names given_column_names

  sql = "insert or replace into " + @name +
      "(" + columns.map(&:name).join(', ') + ") "

  ag = Ferret::Alias_Generator.new [@name, *@fields.keys]
  old_alias, new_alias = %w{old new}.map do |prefix|
    ag.available?(prefix) ?
        ag.reserve(prefix) :
        ag.create(prefix)
  end

  # Specify which field values are new and which ones are to
  # be retained (or initialised from defaults)
  sql << "select " << columns.map{|column| '%s.%s' % [
    given_columns.include?(column) ? new_alias : old_alias,
    column.name,
  ]}.join(', ')

  # Encode the changes as a subquery
  sql << " from (select " << given_column_names.map{|fn|
      ":#{fn} as #{fn}"}.join(', ') << ")"

  # Left-join the subquery against the preƫxisting table
  sql << (" as %{new} left join %{table} as %{old} " +
      "on %{new}.%{key} = %{old}.%{key}") % {
    :old => old_alias,
    :new => new_alias,
    :key => key_column.name,
    :table => @name,
  }

  return sql
end
sql_to_create() click to toggle source
# File lib/sql-ferret.rb, line 718
def sql_to_create
  # No trailing semicolon.
  return "create table #{name} (\n  " +
      @fields.values.select(&:column?).
          map(&:sql_to_declare).join(",\n  ") +
      ")"
end
sql_to_insert(given_column_names) click to toggle source
# File lib/sql-ferret.rb, line 672
def sql_to_insert given_column_names
  ugh? table: @name do
    # We have to check this, lest we generate broken SQL.
    ugh 'inserting-null-tuple' \
        if given_column_names.empty?

    given_columns = resolve_column_names given_column_names

    # Check that all the mandatory fields are given
    @fields.each_value do |field|
      next if field.optional? or field.default
      next if given_columns.include? field
      # SQLite can autopopulate the [[integer primary key]]
      # field.
      next if field.primary_key? and field.type == 'integer'
      ugh 'mandatory-value-missing',
          table: @name,
          column: field.name,
          given_columns: given_columns.map(&:name).join(' ')
    end

    return "insert into " +
        "#{@name}(#{given_columns.map(&:name).join ', '}) " +
        "values(:#{given_column_names.join ', :'})"
  end
end