Class: Ferret::Table

Inherits:
Object
  • Object
show all
Defined in:
lib/sql-ferret.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name) ⇒ Table

Returns a new instance of Table.



551
552
553
554
555
556
557
# 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

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



550
551
552
# File 'lib/sql-ferret.rb', line 550

def name
  @name
end

#primary_keyObject (readonly)

FIXME: move to the section for data model



576
577
578
# File 'lib/sql-ferret.rb', line 576

def primary_key
  @primary_key
end

Instance Method Details

#[](name) ⇒ Object



559
560
561
# File 'lib/sql-ferret.rb', line 559

def [] name
  return @fields[name.downcase]
end

#add_field(field) ⇒ Object

[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.



584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
# 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

#columnsObject



567
568
569
# File 'lib/sql-ferret.rb', line 567

def columns
  return @fields.values.select(&:column?)
end

#empty?Boolean

Returns:

  • (Boolean)


563
564
565
# File 'lib/sql-ferret.rb', line 563

def empty?
  return @fields.empty?
end

#has_columns?Boolean

Returns:

  • (Boolean)


571
572
573
# File 'lib/sql-ferret.rb', line 571

def has_columns?
  return @fields.values.any?(&:column?)
end

#resolve_column_names(names) ⇒ Object



699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
# 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) ⇒ Object

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.



648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
# 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) ⇒ Object



605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
# 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_createObject



718
719
720
721
722
723
724
# 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) ⇒ Object



672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
# 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