og 0.24.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ProjectInfo +2 -5
- data/README +2 -0
- data/doc/AUTHORS +4 -1
- data/doc/RELEASES +53 -0
- data/examples/run.rb +2 -2
- data/lib/{og/mixin → glue}/hierarchical.rb +19 -19
- data/lib/{og/mixin → glue}/optimistic_locking.rb +1 -1
- data/lib/glue/orderable.rb +235 -0
- data/lib/glue/revisable.rb +2 -0
- data/lib/glue/taggable.rb +176 -0
- data/lib/{og/mixin/taggable.rb → glue/taggable_old.rb} +6 -0
- data/lib/glue/timestamped.rb +37 -0
- data/lib/{og/mixin → glue}/tree.rb +3 -8
- data/lib/og.rb +21 -20
- data/lib/og/collection.rb +15 -1
- data/lib/og/entity.rb +256 -114
- data/lib/og/manager.rb +60 -27
- data/lib/og/{mixin/schema_inheritance_base.rb → markers.rb} +5 -2
- data/lib/og/relation.rb +70 -74
- data/lib/og/relation/belongs_to.rb +5 -3
- data/lib/og/relation/has_many.rb +1 -0
- data/lib/og/relation/joins_many.rb +5 -4
- data/lib/og/store.rb +25 -46
- data/lib/og/store/alpha/filesys.rb +1 -1
- data/lib/og/store/alpha/kirby.rb +30 -30
- data/lib/og/store/alpha/memory.rb +49 -49
- data/lib/og/store/alpha/sqlserver.rb +7 -7
- data/lib/og/store/kirby.rb +38 -38
- data/lib/og/store/mysql.rb +43 -43
- data/lib/og/store/psql.rb +222 -53
- data/lib/og/store/sql.rb +165 -105
- data/lib/og/store/sqlite.rb +29 -25
- data/lib/og/validation.rb +24 -14
- data/lib/{vendor → og/vendor}/README +0 -0
- data/lib/{vendor → og/vendor}/kbserver.rb +1 -1
- data/lib/{vendor → og/vendor}/kirbybase.rb +230 -79
- data/lib/{vendor → og/vendor}/mysql.rb +0 -0
- data/lib/{vendor → og/vendor}/mysql411.rb +0 -0
- data/test/og/mixin/tc_hierarchical.rb +1 -1
- data/test/og/mixin/tc_optimistic_locking.rb +1 -1
- data/test/og/mixin/tc_orderable.rb +1 -1
- data/test/og/mixin/tc_taggable.rb +2 -2
- data/test/og/mixin/tc_timestamped.rb +2 -2
- data/test/og/tc_finder.rb +33 -0
- data/test/og/tc_inheritance.rb +2 -2
- data/test/og/tc_scoped.rb +45 -0
- data/test/og/tc_store.rb +1 -7
- metadata +21 -18
- data/lib/og/mixin/orderable.rb +0 -174
- data/lib/og/mixin/revisable.rb +0 -0
- data/lib/og/mixin/timestamped.rb +0 -24
    
        data/lib/og/store/psql.rb
    CHANGED
    
    | @@ -5,6 +5,63 @@ rescue Object => ex | |
| 5 5 | 
             
              Logger.error ex
         | 
| 6 6 | 
             
            end
         | 
| 7 7 |  | 
| 8 | 
            +
            class PGconn
         | 
| 9 | 
            +
              # Lists all the tables within the database.
         | 
| 10 | 
            +
              
         | 
| 11 | 
            +
              def list_tables 
         | 
| 12 | 
            +
                r = self.exec "SELECT c.relname FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind='r' AND n.nspname NOT IN ('pg_catalog', 'pg_toast') AND pg_catalog.pg_table_is_visible(c.oid)"
         | 
| 13 | 
            +
                ret = r.result.flatten
         | 
| 14 | 
            +
                r.clear
         | 
| 15 | 
            +
                ret
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              # Returns true if a table exists within the database, false 
         | 
| 19 | 
            +
              # otherwise.
         | 
| 20 | 
            +
              
         | 
| 21 | 
            +
              def table_exists?(table) #rp: this should be abstracted to the sql abstractor
         | 
| 22 | 
            +
                r = self.exec "SELECT c.relname FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind='r' AND n.nspname NOT IN ('pg_catalog', 'pg_toast') AND pg_catalog.pg_table_is_visible(c.oid) AND c.relname='#{self.class.escape(table.to_s)}'"
         | 
| 23 | 
            +
                ret = r.result.size != 0
         | 
| 24 | 
            +
                r.clear
         | 
| 25 | 
            +
                ret
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              # Returns the PostgreSQL OID of a table within the database or 
         | 
| 29 | 
            +
              # nil if it doesn't exist. Mostly for internal usage.
         | 
| 30 | 
            +
              
         | 
| 31 | 
            +
              def table_oid(table)
         | 
| 32 | 
            +
                r = self.exec "SELECT c.oid FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind='r' AND n.nspname NOT IN ('pg_catalog', 'pg_toast') AND pg_catalog.pg_table_is_visible(c.oid) AND c.relname='#{self.class.escape(table.to_s)}'"
         | 
| 33 | 
            +
                ret = r.result.flatten.first
         | 
| 34 | 
            +
                r.clear
         | 
| 35 | 
            +
                ret
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              # Returns an array of arrays containing the list of fields within a
         | 
| 39 | 
            +
              # table. Each element contains two elements, the first is the field
         | 
| 40 | 
            +
              # name and the second is the field type. Returns nil if the table
         | 
| 41 | 
            +
              # does not exist.
         | 
| 42 | 
            +
              
         | 
| 43 | 
            +
              def table_field_list(table)
         | 
| 44 | 
            +
                return nil unless pg_oid = table_oid(table)
         | 
| 45 | 
            +
                r = self.exec "SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod) FROM pg_catalog.pg_attribute a WHERE a.attrelid = '#{pg_oid}' AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum"
         | 
| 46 | 
            +
                ret = r.result
         | 
| 47 | 
            +
                r.clear
         | 
| 48 | 
            +
                ret
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
              # Returns an array of arrays containing the PostgreSQL foreign keys
         | 
| 52 | 
            +
              # within a table. The first element is the constraint name and the
         | 
| 53 | 
            +
              # second element is the constraint definition.
         | 
| 54 | 
            +
              
         | 
| 55 | 
            +
              def table_foreign_keys(table)
         | 
| 56 | 
            +
                return nil unless pg_oid = table_oid(table)
         | 
| 57 | 
            +
                r = self.exec "SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_catalog.pg_constraint r WHERE r.conrelid = '#{pg_oid}' AND r.contype = 'f'"
         | 
| 58 | 
            +
                ret = r.result
         | 
| 59 | 
            +
                r.clear
         | 
| 60 | 
            +
                ret
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            end
         | 
| 64 | 
            +
             | 
| 8 65 | 
             
            require 'og/store/sql'
         | 
| 9 66 |  | 
| 10 67 | 
             
            # Customize the standard postgres resultset to make
         | 
| @@ -71,6 +128,8 @@ end | |
| 71 128 | 
             
            # A Store that persists objects into a PostgreSQL database.
         | 
| 72 129 | 
             
            # To read documentation about the methods, consult the documentation
         | 
| 73 130 | 
             
            # for SqlStore and Store.
         | 
| 131 | 
            +
            #
         | 
| 132 | 
            +
            # This is the reference Og store.
         | 
| 74 133 | 
             
            # 
         | 
| 75 134 | 
             
            # === Design
         | 
| 76 135 | 
             
            #
         | 
| @@ -92,6 +151,28 @@ class PsqlStore < SqlStore | |
| 92 151 | 
             
                super
         | 
| 93 152 | 
             
              end
         | 
| 94 153 |  | 
| 154 | 
            +
              # Purges all tables from the database.
         | 
| 155 | 
            +
              
         | 
| 156 | 
            +
              def self.destroy_tables(options)
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                conn = PGconn.connect(
         | 
| 159 | 
            +
                  options[:address],
         | 
| 160 | 
            +
                  options[:port], nil, nil,
         | 
| 161 | 
            +
                  options[:name],
         | 
| 162 | 
            +
                  options[:user].to_s,
         | 
| 163 | 
            +
                  options[:password].to_s
         | 
| 164 | 
            +
                )
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                conn.list_tables.each do |table|
         | 
| 167 | 
            +
                  sql = "DROP TABLE #{table} CASCADE"
         | 
| 168 | 
            +
                  conn.exec sql
         | 
| 169 | 
            +
                  Logger.debug "Dropped database table #{table}"
         | 
| 170 | 
            +
                end
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                conn.close
         | 
| 173 | 
            +
              end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
             | 
| 95 176 | 
             
              def initialize(options)
         | 
| 96 177 | 
             
                super
         | 
| 97 178 |  | 
| @@ -134,7 +215,7 @@ class PsqlStore < SqlStore | |
| 134 215 | 
             
                  klass.const_set 'OGSEQ', "#{table(klass)}_oid_seq"
         | 
| 135 216 | 
             
                end
         | 
| 136 217 |  | 
| 137 | 
            -
                if klass.ann. | 
| 218 | 
            +
                if klass.ann.self.primary_key.symbol == :oid
         | 
| 138 219 | 
             
                  unless klass.properties.include? :oid
         | 
| 139 220 | 
             
                    klass.property :oid, Fixnum, :sql => 'serial PRIMARY KEY'
         | 
| 140 221 | 
             
                  end
         | 
| @@ -165,7 +246,7 @@ class PsqlStore < SqlStore | |
| 165 246 | 
             
              end
         | 
| 166 247 |  | 
| 167 248 | 
             
              # Start a new transaction.
         | 
| 168 | 
            -
             | 
| 249 | 
            +
             | 
| 169 250 | 
             
              def start  
         | 
| 170 251 | 
             
                # neumann: works with earlier PSQL databases too.
         | 
| 171 252 | 
             
                exec('BEGIN TRANSACTION') if @transaction_nesting < 1
         | 
| @@ -174,79 +255,166 @@ class PsqlStore < SqlStore | |
| 174 255 |  | 
| 175 256 | 
             
            private
         | 
| 176 257 |  | 
| 177 | 
            -
               | 
| 178 | 
            -
             | 
| 258 | 
            +
              # Adds foreign key constraints to a join table, replicating all
         | 
| 259 | 
            +
              # modifications to OIDs to the join tables and also purging
         | 
| 260 | 
            +
              # any left over data from deleted records (at the time of
         | 
| 261 | 
            +
              # implementation, self-join cases left this data here).
         | 
| 179 262 |  | 
| 180 | 
            -
             | 
| 181 | 
            -
             | 
| 182 | 
            -
                 | 
| 183 | 
            -
             | 
| 184 | 
            -
                 | 
| 185 | 
            -
             | 
| 263 | 
            +
              def create_join_table_foreign_key_constraints(klass,info)
         | 
| 264 | 
            +
             | 
| 265 | 
            +
                table_list = @conn.list_tables
         | 
| 266 | 
            +
             | 
| 267 | 
            +
                needed_tables = [ info[:table], info[:first_table], info[:second_table] ]
         | 
| 268 | 
            +
                missing_tables = Array.new
         | 
| 269 | 
            +
                needed_tables.each do |table|
         | 
| 270 | 
            +
                  missing_tables << table unless table_list.include?(table)
         | 
| 186 271 | 
             
                end
         | 
| 187 | 
            -
                
         | 
| 188 | 
            -
             | 
| 189 | 
            -
             | 
| 190 | 
            -
             | 
| 191 | 
            -
             | 
| 192 | 
            -
             | 
| 193 | 
            -
                  for data in indices
         | 
| 194 | 
            -
                    idx, options = *data
         | 
| 195 | 
            -
                    idx = idx.to_s
         | 
| 196 | 
            -
                    pre_sql, post_sql = options[:pre], options[:post]
         | 
| 197 | 
            -
                    idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
         | 
| 198 | 
            -
                    sql << " CREATE #{pre_sql} INDEX #{klass::OGTABLE}_#{idxname}_idx #{post_sql} ON #{klass::OGTABLE} (#{idx});"  
         | 
| 272 | 
            +
                if missing_tables.size > 0
         | 
| 273 | 
            +
                  msg = "Join table #{info[:table]} needs PostgreSQL foreign key constraints but the following table"
         | 
| 274 | 
            +
                  msg << (missing_tables.size > 1 ? "s were " : " was ")
         | 
| 275 | 
            +
                  msg << "missing: "
         | 
| 276 | 
            +
                  missing_tables.each do |table|
         | 
| 277 | 
            +
                    msg << "#{table}, "
         | 
| 199 278 | 
             
                  end
         | 
| 279 | 
            +
                  Logger.debug msg[0..-3] + ". (Should be retried later)."
         | 
| 280 | 
            +
                  return false
         | 
| 200 281 | 
             
                end
         | 
| 201 282 |  | 
| 202 | 
            -
                 | 
| 203 | 
            -
             | 
| 204 | 
            -
             | 
| 205 | 
            -
             | 
| 206 | 
            -
             | 
| 207 | 
            -
             | 
| 208 | 
            -
             | 
| 209 | 
            -
             | 
| 210 | 
            -
             | 
| 211 | 
            -
             | 
| 283 | 
            +
                #This info should maybe be in join metadata?
         | 
| 284 | 
            +
                target_class = nil
         | 
| 285 | 
            +
                klass.relations.each do |rel|
         | 
| 286 | 
            +
                  if rel.join_table == info[:table]
         | 
| 287 | 
            +
                    target_class =  rel.target_class
         | 
| 288 | 
            +
                    break
         | 
| 289 | 
            +
                  end
         | 
| 290 | 
            +
                end
         | 
| 291 | 
            +
             | 
| 292 | 
            +
                if target_class
         | 
| 293 | 
            +
                  Logger.debug "Adding PostgreSQL foreign key constraints to #{info[:table]}"
         | 
| 294 | 
            +
             | 
| 295 | 
            +
                  # TODO: This should also interrograte the constraint definition
         | 
| 296 | 
            +
                  # incase people meddle with the database in an insane fashion
         | 
| 297 | 
            +
                  # (very, very low priority)
         | 
| 298 | 
            +
                  
         | 
| 299 | 
            +
                  existing_constraints = @conn.table_foreign_keys(info[:table]).map {|fk| fk.first}
         | 
| 300 | 
            +
             | 
| 301 | 
            +
                  constraints = Array.new
         | 
| 302 | 
            +
                  constraints << { :table => info[:first_table], :join_table => info[:table], :primary_key => klass.primary_key.field || klass.primary_key.symbol, :foreign_key => info[:first_key] }
         | 
| 303 | 
            +
                  constraints << { :table => info[:second_table], :join_table => info[:table], :primary_key => target_class.primary_key.field || target_class.primary_key.symbol, :foreign_key => info[:second_key] }
         | 
| 304 | 
            +
             | 
| 305 | 
            +
                  constraints.each do |constraint|
         | 
| 306 | 
            +
                    constraint_name = "ogc_#{constraint[:table]}_#{constraint[:foreign_key]}"
         | 
| 307 | 
            +
                    if existing_constraints.include?(constraint_name)
         | 
| 308 | 
            +
                      Logger.debug "PostgreSQL foreign key constraint linking #{constraint[:foreign_key]} on #{constraint[:join_table]} to #{constraint[:primary_key]} on #{constraint[:table]} already exists (#{constraint_name})."
         | 
| 309 | 
            +
                      next
         | 
| 310 | 
            +
                    end
         | 
| 311 | 
            +
                    sql = "ALTER TABLE #{constraint[:join_table]} ADD CONSTRAINT #{constraint_name} FOREIGN KEY (#{constraint[:foreign_key]}) REFERENCES #{constraint[:table]} (#{constraint[:primary_key]}) ON UPDATE CASCADE ON DELETE CASCADE"
         | 
| 312 | 
            +
                    @conn.exec(sql).clear
         | 
| 313 | 
            +
                    Logger.debug "Added PostgreSQL foreign key constraint linking #{constraint[:foreign_key]} on #{constraint[:join_table]} to #{constraint[:primary_key]} on #{constraint[:table]} (#{constraint_name})."
         | 
| 314 | 
            +
                  end
         | 
| 315 | 
            +
                end
         | 
| 316 | 
            +
              end
         | 
| 317 | 
            +
             | 
| 318 | 
            +
              def create_table(klass)
         | 
| 319 | 
            +
                fields = fields_for_class(klass)
         | 
| 320 | 
            +
             | 
| 321 | 
            +
                unless @conn.table_exists? klass::OGTABLE
         | 
| 322 | 
            +
             | 
| 323 | 
            +
                  sql = "CREATE TABLE #{klass::OGTABLE} (#{fields.join(', ')}"
         | 
| 324 | 
            +
             | 
| 325 | 
            +
                  # Create table constraints.
         | 
| 326 | 
            +
             | 
| 327 | 
            +
                  if constraints = klass.ann.self[:sql_constraint]
         | 
| 328 | 
            +
                    sql << ", #{constraints.join(', ')}"
         | 
| 329 | 
            +
                  end
         | 
| 330 | 
            +
             | 
| 331 | 
            +
                  sql << ") WITHOUT OIDS;"
         | 
| 332 | 
            +
             | 
| 333 | 
            +
                  # Create indices.
         | 
| 334 | 
            +
             | 
| 335 | 
            +
                  if indices = klass.ann.self[:index]
         | 
| 336 | 
            +
                    for data in indices
         | 
| 337 | 
            +
                      idx, options = *data
         | 
| 338 | 
            +
                      idx = idx.to_s
         | 
| 339 | 
            +
                      pre_sql, post_sql = options[:pre], options[:post]
         | 
| 340 | 
            +
                      idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
         | 
| 341 | 
            +
                      sql << " CREATE #{pre_sql} INDEX #{klass::OGTABLE}_#{idxname}_idx #{post_sql} ON #{klass::OGTABLE} (#{idx});"  
         | 
| 342 | 
            +
                    end
         | 
| 343 | 
            +
                  end
         | 
| 344 | 
            +
             | 
| 345 | 
            +
                    @conn.exec(sql).clear
         | 
| 346 | 
            +
                    Logger.info "Created table '#{klass::OGTABLE}'."
         | 
| 347 | 
            +
                else
         | 
| 348 | 
            +
                  Logger.debug "Table #{klass::OGTABLE} already exists"
         | 
| 349 | 
            +
                  #rp: basic field interrogation
         | 
| 350 | 
            +
                  # TODO: Add type checking.
         | 
| 351 | 
            +
             | 
| 352 | 
            +
                  actual_fields = @conn.table_field_list(klass::OGTABLE).map {|pair| pair.first}
         | 
| 353 | 
            +
             | 
| 354 | 
            +
                  #Make new ones always - don't destroy by default because it might contain data you want back.
         | 
| 355 | 
            +
                  need_fields = fields.each do |needed_field|
         | 
| 356 | 
            +
                    field_name = needed_field[0..(needed_field.index(' ')-1)]
         | 
| 357 | 
            +
                    next if actual_fields.include?(field_name)
         | 
| 358 | 
            +
             | 
| 359 | 
            +
                    if @options[:evolve_schema] == true
         | 
| 360 | 
            +
                      Logger.debug "Adding field '#{needed_field}' to '#{klass::OGTABLE}'"
         | 
| 361 | 
            +
                      sql = "ALTER TABLE #{klass::OGTABLE} ADD COLUMN #{needed_field}"
         | 
| 362 | 
            +
                      @conn.exec(sql)
         | 
| 363 | 
            +
                    else
         | 
| 364 | 
            +
                      Logger.info "WARNING: Table '#{klass::OGTABLE}' is missing field '#{needed_field}' and :evolve_schema is not set to true!"
         | 
| 365 | 
            +
                    end
         | 
| 366 | 
            +
                  end
         | 
| 367 | 
            +
             | 
| 368 | 
            +
                  #Drop old ones
         | 
| 369 | 
            +
                  needed_fields = fields.map {|f| f =~ /^([^ ]+)/; $1}
         | 
| 370 | 
            +
                  actual_fields.each do |obsolete_field|
         | 
| 371 | 
            +
                    next if needed_fields.include?(obsolete_field)
         | 
| 372 | 
            +
                    if @options[:evolve_schema] == true and @options[:evolve_schema_cautious] == false
         | 
| 373 | 
            +
                      sql = "ALTER TABLE #{klass::OGTABLE} DROP COLUMN #{obsolete_field}"
         | 
| 374 | 
            +
                      Logger.debug "Removing obsolete field '#{obsolete_field}' from '#{klass::OGTABLE}'"
         | 
| 375 | 
            +
                      @conn.exec(sql)
         | 
| 376 | 
            +
                   else
         | 
| 377 | 
            +
                      Logger.info "WARNING: You have an obsolete field '#{obsolete_field}' on table '#{klass::OGTABLE}' and :evolve_schema is not set or is in cautious mode!"
         | 
| 378 | 
            +
                    end
         | 
| 212 379 | 
             
                  end
         | 
| 213 380 | 
             
                end
         | 
| 214 381 |  | 
| 215 382 | 
             
                # Create join tables if needed. Join tables are used in
         | 
| 216 383 | 
             
                # 'many_to_many' relations.
         | 
| 217 | 
            -
             | 
| 218 | 
            -
             | 
| 219 | 
            -
                   | 
| 220 | 
            -
             | 
| 221 | 
            -
             | 
| 222 | 
            -
             | 
| 223 | 
            -
             | 
| 224 | 
            -
             | 
| 225 | 
            -
             | 
| 226 | 
            -
             | 
| 227 | 
            -
                      if ex.to_s =~ /relation .* already exists/i
         | 
| 228 | 
            -
                        Logger.debug 'Join table already exists' if $DBG
         | 
| 384 | 
            +
                  players = klass.resolve_remote_relations.map{|rel| rel.owner_class if rel.collection}.compact.uniq
         | 
| 385 | 
            +
                  players << klass
         | 
| 386 | 
            +
                  players.each do |player|
         | 
| 387 | 
            +
                  if join_tables = player.ann.self[:join_tables] 
         | 
| 388 | 
            +
                    for info in join_tables
         | 
| 389 | 
            +
                      unless @conn.table_exists? info[:table]
         | 
| 390 | 
            +
                        create_join_table_sql(info).each do |sql|
         | 
| 391 | 
            +
                          @conn.exec(sql).clear
         | 
| 392 | 
            +
                        end
         | 
| 393 | 
            +
                        Logger.debug "Created jointable '#{info[:table]}'."
         | 
| 229 394 | 
             
                      else
         | 
| 230 | 
            -
                         | 
| 395 | 
            +
                        Logger.debug "Join table '#{info[:table]}' already exists."
         | 
| 231 396 | 
             
                      end
         | 
| 397 | 
            +
                      create_join_table_foreign_key_constraints(player,info)
         | 
| 232 398 | 
             
                    end
         | 
| 233 399 | 
             
                  end
         | 
| 234 400 | 
             
                end
         | 
| 401 | 
            +
             | 
| 235 402 | 
             
              end
         | 
| 236 403 |  | 
| 237 404 | 
             
              def drop_table(klass)
         | 
| 238 | 
            -
                 | 
| 239 | 
            -
                 | 
| 405 | 
            +
                # foreign key constraints will remove the need to do manual cleanup on
         | 
| 406 | 
            +
                # postgresql join tables.
         | 
| 407 | 
            +
                exec "DROP TABLE #{klass.table} CASCADE"
         | 
| 240 408 | 
             
              end
         | 
| 241 409 |  | 
| 242 410 | 
             
              def create_field_map(klass)
         | 
| 243 411 | 
             
                res = @conn.exec "SELECT * FROM #{klass::OGTABLE} LIMIT 1"
         | 
| 244 412 | 
             
                map = {}
         | 
| 245 | 
            -
             | 
| 413 | 
            +
             | 
| 246 414 | 
             
                for field in res.fields
         | 
| 247 415 | 
             
                  map[field.intern] = res.fieldnum(field)
         | 
| 248 416 | 
             
                end
         | 
| 249 | 
            -
             | 
| 417 | 
            +
             | 
| 250 418 | 
             
                return map
         | 
| 251 419 | 
             
              ensure
         | 
| 252 420 | 
             
                res.clear if res
         | 
| @@ -284,23 +452,23 @@ private | |
| 284 452 | 
             
                  props << Property.new(:symbol => :ogtype, :klass => String)
         | 
| 285 453 | 
             
                  values << ", '#{klass}'"
         | 
| 286 454 | 
             
                end
         | 
| 287 | 
            -
             | 
| 455 | 
            +
             | 
| 288 456 | 
             
                sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| field_for_property(p)}.join(',')}) VALUES (#{values})"
         | 
| 289 457 |  | 
| 290 458 | 
             
                klass.class_eval %{ 
         | 
| 291 459 | 
             
                  def og_insert(store)
         | 
| 292 | 
            -
                    #{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
         | 
| 460 | 
            +
                    #{Glue::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
         | 
| 293 461 | 
             
                    res = store.conn.exec "SELECT nextval('#{klass::OGSEQ}')"
         | 
| 294 462 | 
             
                    @#{klass.pk_symbol} = res.getvalue(0, 0).to_i
         | 
| 295 463 | 
             
                    res.clear
         | 
| 296 464 | 
             
                    store.conn.exec("#{sql}").clear
         | 
| 297 | 
            -
                    #{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
         | 
| 465 | 
            +
                    #{Glue::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
         | 
| 298 466 | 
             
                  end
         | 
| 299 467 | 
             
                }
         | 
| 300 468 | 
             
              end
         | 
| 301 469 |  | 
| 302 470 | 
             
              def eval_og_allocate(klass)  
         | 
| 303 | 
            -
                if klass.ann. | 
| 471 | 
            +
                if klass.ann.self[:subclasses]
         | 
| 304 472 | 
             
                  klass.module_eval %{
         | 
| 305 473 | 
             
                    def self.og_allocate(res, row = 0)
         | 
| 306 474 | 
             
                      Object.constant(res.getvalue(row, 0)).allocate
         | 
| @@ -328,3 +496,4 @@ end | |
| 328 496 | 
             
            # * George Moschovitis <gm@navel.gr>
         | 
| 329 497 | 
             
            # * Michael Neumann <mneumann@ntecs.de>
         | 
| 330 498 | 
             
            # * Ysabel <deb@ysabel.org>
         | 
| 499 | 
            +
            # * Rob Pitt <rob@motionpath.com>
         | 
    
        data/lib/og/store/sql.rb
    CHANGED
    
    | @@ -8,7 +8,7 @@ module Og | |
| 8 8 | 
             
            module SqlUtils
         | 
| 9 9 |  | 
| 10 10 | 
             
              # Escape an SQL string
         | 
| 11 | 
            -
             | 
| 11 | 
            +
             | 
| 12 12 | 
             
              def escape(str)
         | 
| 13 13 | 
             
                return nil unless str
         | 
| 14 14 | 
             
                return str.gsub(/'/, "''")
         | 
| @@ -16,19 +16,19 @@ module SqlUtils | |
| 16 16 |  | 
| 17 17 | 
             
              # Convert a ruby time to an sql timestamp.
         | 
| 18 18 | 
             
              #--
         | 
| 19 | 
            -
              # TODO: Optimize this
         | 
| 19 | 
            +
              # TODO: Optimize this.
         | 
| 20 20 | 
             
              #++
         | 
| 21 | 
            -
             | 
| 21 | 
            +
             | 
| 22 22 | 
             
              def timestamp(time = Time.now)
         | 
| 23 23 | 
             
                return nil unless time
         | 
| 24 24 | 
             
                return time.strftime("%Y-%m-%d %H:%M:%S")
         | 
| 25 25 | 
             
              end
         | 
| 26 | 
            -
             | 
| 26 | 
            +
             | 
| 27 27 | 
             
              # Output YYY-mm-dd
         | 
| 28 28 | 
             
              #--
         | 
| 29 29 | 
             
              # TODO: Optimize this.
         | 
| 30 30 | 
             
              #++
         | 
| 31 | 
            -
             | 
| 31 | 
            +
             | 
| 32 32 | 
             
              def date(date)
         | 
| 33 33 | 
             
                return nil unless date
         | 
| 34 34 | 
             
                return "#{date.year}-#{date.month}-#{date.mday}" 
         | 
| @@ -37,40 +37,40 @@ module SqlUtils | |
| 37 37 | 
             
              #--
         | 
| 38 38 | 
             
              # TODO: implement me!
         | 
| 39 39 | 
             
              #++
         | 
| 40 | 
            -
             | 
| 40 | 
            +
             | 
| 41 41 | 
             
              def blob(val)
         | 
| 42 42 | 
             
                val
         | 
| 43 43 | 
             
              end
         | 
| 44 44 |  | 
| 45 45 | 
             
              # Parse an integer.
         | 
| 46 | 
            -
             | 
| 46 | 
            +
             | 
| 47 47 | 
             
              def parse_int(int)
         | 
| 48 48 | 
             
                int = int.to_i if int
         | 
| 49 49 | 
             
                int
         | 
| 50 50 | 
             
              end
         | 
| 51 | 
            -
             | 
| 51 | 
            +
             | 
| 52 52 | 
             
              # Parse a float.
         | 
| 53 | 
            -
             | 
| 53 | 
            +
             | 
| 54 54 | 
             
              def parse_float(fl)
         | 
| 55 55 | 
             
                fl = fl.to_f if fl
         | 
| 56 56 | 
             
                fl
         | 
| 57 57 | 
             
              end
         | 
| 58 | 
            -
             | 
| 58 | 
            +
             | 
| 59 59 | 
             
              # Parse sql datetime
         | 
| 60 60 | 
             
              #--
         | 
| 61 61 | 
             
              # TODO: Optimize this.
         | 
| 62 62 | 
             
              #++
         | 
| 63 | 
            -
             | 
| 63 | 
            +
             | 
| 64 64 | 
             
              def parse_timestamp(str)
         | 
| 65 65 | 
             
                return nil unless str
         | 
| 66 66 | 
             
                return Time.parse(str)    
         | 
| 67 67 | 
             
              end
         | 
| 68 | 
            -
             | 
| 68 | 
            +
             | 
| 69 69 | 
             
              # Input YYYY-mm-dd
         | 
| 70 70 | 
             
              #--
         | 
| 71 71 | 
             
              # TODO: Optimize this.
         | 
| 72 72 | 
             
              #++
         | 
| 73 | 
            -
             | 
| 73 | 
            +
             | 
| 74 74 | 
             
              def parse_date(str)
         | 
| 75 75 | 
             
                return nil unless str
         | 
| 76 76 | 
             
                return Date.strptime(str)
         | 
| @@ -79,13 +79,13 @@ module SqlUtils | |
| 79 79 | 
             
              #--
         | 
| 80 80 | 
             
              # TODO: implement me!!
         | 
| 81 81 | 
             
              #++
         | 
| 82 | 
            -
             | 
| 82 | 
            +
             | 
| 83 83 | 
             
              def parse_blob(val)
         | 
| 84 84 | 
             
                val
         | 
| 85 85 | 
             
              end
         | 
| 86 | 
            -
             | 
| 86 | 
            +
             | 
| 87 87 | 
             
              # Escape the various Ruby types.
         | 
| 88 | 
            -
             | 
| 88 | 
            +
             | 
| 89 89 | 
             
              def quote(val)
         | 
| 90 90 | 
             
                case val
         | 
| 91 91 | 
             
                  when Fixnum, Integer, Float
         | 
| @@ -98,20 +98,20 @@ module SqlUtils | |
| 98 98 | 
             
                    val ? "'#{date(val)}'" : 'NULL'
         | 
| 99 99 | 
             
                  when TrueClass
         | 
| 100 100 | 
             
                    val ? "'t'" : 'NULL'
         | 
| 101 | 
            -
                  else | 
| 101 | 
            +
                  else
         | 
| 102 102 | 
             
                    # gmosx: keep the '' for nil symbols.
         | 
| 103 103 | 
             
                    val ? escape(val.to_yaml) : ''
         | 
| 104 | 
            -
                end | 
| 104 | 
            +
                end
         | 
| 105 105 | 
             
              end
         | 
| 106 106 |  | 
| 107 107 | 
             
              # Apply table name conventions to a class name.
         | 
| 108 | 
            -
             | 
| 108 | 
            +
             | 
| 109 109 | 
             
              def tableize(klass)
         | 
| 110 110 | 
             
                "#{klass.to_s.gsub(/::/, "_").downcase}"
         | 
| 111 111 | 
             
              end
         | 
| 112 112 |  | 
| 113 113 | 
             
              def table(klass)
         | 
| 114 | 
            -
                klass.ann. | 
| 114 | 
            +
                klass.ann.self[:sql_table] || klass.ann.self[:table] || "#{Og.table_prefix}#{tableize(klass)}"
         | 
| 115 115 | 
             
              end
         | 
| 116 116 |  | 
| 117 117 | 
             
              def join_object_ordering(obj1, obj2)
         | 
| @@ -129,7 +129,7 @@ module SqlUtils | |
| 129 129 | 
             
                  return class2, class1, true
         | 
| 130 130 | 
             
                end
         | 
| 131 131 | 
             
              end
         | 
| 132 | 
            -
             | 
| 132 | 
            +
             | 
| 133 133 | 
             
              def build_join_name(class1, class2, postfix = nil)
         | 
| 134 134 | 
             
                # Don't reorder arguments, as this is used in places that
         | 
| 135 135 | 
             
                # have already determined the order they want.
         | 
| @@ -149,14 +149,14 @@ module SqlUtils | |
| 149 149 | 
             
                klass = klass.schema_inheritance_root_class if klass.schema_inheritance_child?
         | 
| 150 150 | 
             
                "#{klass.to_s.split('::').last.downcase}_oid"
         | 
| 151 151 | 
             
              end
         | 
| 152 | 
            -
             | 
| 152 | 
            +
             | 
| 153 153 | 
             
              def join_table_keys(class1, class2)
         | 
| 154 154 | 
             
                if class1 == class2
         | 
| 155 155 | 
             
                  # Fix for the self-join case.
         | 
| 156 156 | 
             
                  return join_table_key(class1), "#{join_table_key(class2)}2"
         | 
| 157 157 | 
             
                else
         | 
| 158 158 | 
             
                  return join_table_key(class1), join_table_key(class2)
         | 
| 159 | 
            -
                end | 
| 159 | 
            +
                end
         | 
| 160 160 | 
             
              end
         | 
| 161 161 |  | 
| 162 162 | 
             
              def ordered_join_table_keys(class1, class2)
         | 
| @@ -165,7 +165,7 @@ module SqlUtils | |
| 165 165 | 
             
              end
         | 
| 166 166 |  | 
| 167 167 | 
             
              def join_table_info(owner_class, target_class, postfix = nil)
         | 
| 168 | 
            -
             | 
| 168 | 
            +
             | 
| 169 169 | 
             
                # some fixes for schema inheritance.
         | 
| 170 170 |  | 
| 171 171 | 
             
                owner_class = owner_class.schema_inheritance_root_class if owner_class.schema_inheritance_child?
         | 
| @@ -173,13 +173,13 @@ module SqlUtils | |
| 173 173 |  | 
| 174 174 | 
             
                owner_key, target_key = join_table_keys(owner_class, target_class)
         | 
| 175 175 | 
             
                first, second, changed = join_class_ordering(owner_class, target_class)
         | 
| 176 | 
            -
             | 
| 176 | 
            +
             | 
| 177 177 | 
             
                if changed
         | 
| 178 178 | 
             
                  first_key, second_key = target_key, owner_key
         | 
| 179 179 | 
             
                else
         | 
| 180 180 | 
             
                  first_key, second_key = owner_key, target_key
         | 
| 181 181 | 
             
                end
         | 
| 182 | 
            -
             | 
| 182 | 
            +
             | 
| 183 183 | 
             
                return {
         | 
| 184 184 | 
             
                  :table => join_table(owner_class, target_class, postfix),
         | 
| 185 185 | 
             
                  :owner_key => owner_key,
         | 
| @@ -195,7 +195,7 @@ module SqlUtils | |
| 195 195 |  | 
| 196 196 | 
             
              # Subclasses can override this if they need a different 
         | 
| 197 197 | 
             
              # syntax.
         | 
| 198 | 
            -
             | 
| 198 | 
            +
             | 
| 199 199 | 
             
              def create_join_table_sql(join_table_info, suffix = 'NOT NULL', key_type = 'integer')
         | 
| 200 200 | 
             
                join_table = join_table_info[:table]
         | 
| 201 201 | 
             
                first_index = join_table_info[:first_index]
         | 
| @@ -210,13 +210,13 @@ module SqlUtils | |
| 210 210 | 
             
                    #{first_key} integer NOT NULL,
         | 
| 211 211 | 
             
                    #{second_key} integer NOT NULL,
         | 
| 212 212 | 
             
                    PRIMARY KEY(#{first_key}, #{second_key})
         | 
| 213 | 
            -
                  ) | 
| 213 | 
            +
                  )
         | 
| 214 214 | 
             
                }
         | 
| 215 | 
            -
             | 
| 215 | 
            +
             | 
| 216 216 | 
             
                # gmosx: not that useful?
         | 
| 217 217 | 
             
                # sql << "CREATE INDEX #{first_index} ON #{join_table} (#{first_key})"
         | 
| 218 218 | 
             
                # sql << "CREATE INDEX #{second_index} ON #{join_table} (#{second_key})"
         | 
| 219 | 
            -
             | 
| 219 | 
            +
             | 
| 220 220 | 
             
                return sql
         | 
| 221 221 | 
             
              end
         | 
| 222 222 |  | 
| @@ -237,7 +237,7 @@ class SqlStore < Store | |
| 237 237 |  | 
| 238 238 | 
             
                # The default Ruby <-> SQL type mappings, should be valid 
         | 
| 239 239 | 
             
                # for most RDBM systems.
         | 
| 240 | 
            -
             | 
| 240 | 
            +
             | 
| 241 241 | 
             
                @typemap = {
         | 
| 242 242 | 
             
                  Integer => 'integer',
         | 
| 243 243 | 
             
                  Fixnum => 'integer',
         | 
| @@ -265,12 +265,36 @@ class SqlStore < Store | |
| 265 265 | 
             
                Glue::Aspects.wrap(klass, [:exec, :query])
         | 
| 266 266 | 
             
              end
         | 
| 267 267 |  | 
| 268 | 
            -
              #  | 
| 268 | 
            +
              # Returns a list of tables that exist within the database but are 
         | 
| 269 | 
            +
              # not managed by the supplied manager.
         | 
| 270 | 
            +
              
         | 
| 271 | 
            +
              def unmanaged_tables(manager)
         | 
| 272 | 
            +
                ret = Array.new
         | 
| 273 | 
            +
                mt = managed_tables(manager)
         | 
| 274 | 
            +
                @conn.list_tables.each do |table|
         | 
| 275 | 
            +
                  ret << table unless mt.include?(table)
         | 
| 276 | 
            +
                end
         | 
| 277 | 
            +
                ret
         | 
| 278 | 
            +
              end
         | 
| 279 | 
            +
             | 
| 280 | 
            +
              # Returns a list of tables within the database that are there to 
         | 
| 281 | 
            +
              # support a class managed by the supplied manager.
         | 
| 282 | 
            +
              
         | 
| 283 | 
            +
              def managed_tables(manager)
         | 
| 284 | 
            +
                ret = Array.new
         | 
| 285 | 
            +
                manager.managed_classes.each do |klass|
         | 
| 286 | 
            +
                  ret << klass::OGTABLE
         | 
| 287 | 
            +
                  ret.concat(klass.relations.reject{|rel| not rel.options[:join_table]}.map{|rel| rel.options[:join_table]})
         | 
| 288 | 
            +
                end
         | 
| 289 | 
            +
                ret
         | 
| 290 | 
            +
              end
         | 
| 291 | 
            +
             | 
| 292 | 
            +
              # Enchants a class.
         | 
| 269 293 |  | 
| 270 294 | 
             
              def enchant(klass, manager)
         | 
| 271 295 |  | 
| 272 296 | 
             
                # setup the table where this class is mapped.
         | 
| 273 | 
            -
             | 
| 297 | 
            +
             | 
| 274 298 | 
             
                if klass.schema_inheritance_child?
         | 
| 275 299 | 
             
                  # farms: allow deeper inheritance (TODO: use annotation :superclass)
         | 
| 276 300 | 
             
                  klass.const_set 'OGTABLE', table(klass.schema_inheritance_root_class)
         | 
| @@ -279,13 +303,13 @@ class SqlStore < Store | |
| 279 303 | 
             
                end
         | 
| 280 304 |  | 
| 281 305 | 
             
                klass.module_eval 'def self.table; OGTABLE; end'
         | 
| 282 | 
            -
             | 
| 306 | 
            +
             | 
| 283 307 | 
             
                eval_og_allocate(klass)
         | 
| 284 | 
            -
             | 
| 308 | 
            +
             | 
| 285 309 | 
             
                super
         | 
| 286 310 |  | 
| 287 311 | 
             
                unless klass.polymorphic_parent?
         | 
| 288 | 
            -
                  # precompile class specific lifecycle methods. | 
| 312 | 
            +
                  # precompile class specific lifecycle methods.
         | 
| 289 313 | 
             
                  eval_og_create_schema(klass)
         | 
| 290 314 | 
             
                  eval_og_insert(klass)
         | 
| 291 315 | 
             
                  eval_og_update(klass)
         | 
| @@ -309,7 +333,9 @@ class SqlStore < Store | |
| 309 333 | 
             
              # Loads an object from the store using the primary key.
         | 
| 310 334 |  | 
| 311 335 | 
             
              def load(pk, klass)
         | 
| 312 | 
            -
                 | 
| 336 | 
            +
                sql = "SELECT * FROM #{klass::OGTABLE} WHERE #{klass.pk_symbol}=#{pk}"
         | 
| 337 | 
            +
                sql << " AND ogtype='#{klass}'" if klass.schema_inheritance_child?
         | 
| 338 | 
            +
                res = query sql
         | 
| 313 339 | 
             
                read_one(res, klass)
         | 
| 314 340 | 
             
              end
         | 
| 315 341 | 
             
              alias_method :exist?, :load
         | 
| @@ -318,7 +344,9 @@ class SqlStore < Store | |
| 318 344 |  | 
| 319 345 | 
             
              def reload(obj, pk)
         | 
| 320 346 | 
             
                raise 'Cannot reload unmanaged object' unless obj.saved?
         | 
| 321 | 
            -
                 | 
| 347 | 
            +
                sql = "SELECT * FROM #{obj.class.table} WHERE #{obj.class.pk_symbol}=#{pk}"
         | 
| 348 | 
            +
                sql << " AND ogtype='#{obj.class}'" if obj.class.schema_inheritance_child?
         | 
| 349 | 
            +
                res = query sql
         | 
| 322 350 | 
             
                obj.og_read(res.next, 0)
         | 
| 323 351 | 
             
              ensure
         | 
| 324 352 | 
             
                res.close if res
         | 
| @@ -330,7 +358,7 @@ class SqlStore < Store | |
| 330 358 | 
             
              #--
         | 
| 331 359 | 
             
              # gmosx, THINK: condition is not really useful here :(
         | 
| 332 360 | 
             
              #++
         | 
| 333 | 
            -
             | 
| 361 | 
            +
             | 
| 334 362 | 
             
              def update(obj, options = nil)
         | 
| 335 363 | 
             
                if options and properties = options[:only]
         | 
| 336 364 | 
             
                  if properties.is_a?(Array)
         | 
| @@ -352,7 +380,7 @@ class SqlStore < Store | |
| 352 380 |  | 
| 353 381 | 
             
              # Update selected properties of an object or class of
         | 
| 354 382 | 
             
              # objects.
         | 
| 355 | 
            -
             | 
| 383 | 
            +
             | 
| 356 384 | 
             
              def update_properties(target, *properties)
         | 
| 357 385 | 
             
                update(target, :only => properties)
         | 
| 358 386 | 
             
              end
         | 
| @@ -360,10 +388,10 @@ class SqlStore < Store | |
| 360 388 | 
             
              alias_method :update_property, :update_properties
         | 
| 361 389 |  | 
| 362 390 | 
             
              # More generalized method, also allows for batch updates.
         | 
| 363 | 
            -
             | 
| 391 | 
            +
             | 
| 364 392 | 
             
              def update_by_sql(target, set, options = nil)
         | 
| 365 393 | 
             
                set = set.gsub(/@/, '')
         | 
| 366 | 
            -
             | 
| 394 | 
            +
             | 
| 367 395 | 
             
                if target.is_a?(Class)
         | 
| 368 396 | 
             
                  sql = "UPDATE #{target.table} SET #{set} "
         | 
| 369 397 | 
             
                  sql << " WHERE #{options[:condition]}" if options and options[:condition]
         | 
| @@ -381,7 +409,7 @@ class SqlStore < Store | |
| 381 409 | 
             
              #
         | 
| 382 410 | 
             
              # User.find(:condition  => 'age > 15', :order => 'score ASC', :offet => 10, :limit =>10)
         | 
| 383 411 | 
             
              # Comment.find(:include => :entry)
         | 
| 384 | 
            -
             | 
| 412 | 
            +
             | 
| 385 413 | 
             
              def find(options)  
         | 
| 386 414 | 
             
                klass = options[:class]
         | 
| 387 415 | 
             
                sql = resolve_options(klass, options)
         | 
| @@ -389,7 +417,7 @@ class SqlStore < Store | |
| 389 417 | 
             
              end
         | 
| 390 418 |  | 
| 391 419 | 
             
              # Find one object.
         | 
| 392 | 
            -
             | 
| 420 | 
            +
             | 
| 393 421 | 
             
              def find_one(options)
         | 
| 394 422 | 
             
                klass = options[:class]
         | 
| 395 423 | 
             
            # gmosx, THINK: should not set this by default.
         | 
| @@ -400,15 +428,15 @@ class SqlStore < Store | |
| 400 428 |  | 
| 401 429 | 
             
              # Perform a custom sql query and deserialize the 
         | 
| 402 430 | 
             
              # results.
         | 
| 403 | 
            -
             | 
| 431 | 
            +
             | 
| 404 432 | 
             
              def select(sql, klass)
         | 
| 405 433 | 
             
                sql = "SELECT * FROM #{klass.table} " + sql unless sql =~ /SELECT/
         | 
| 406 434 | 
             
                read_all(query(sql), klass)
         | 
| 407 435 | 
             
              end
         | 
| 408 436 | 
             
              alias_method :find_by_sql, :select
         | 
| 409 | 
            -
             | 
| 437 | 
            +
             | 
| 410 438 | 
             
              # Specialized one result version of select.
         | 
| 411 | 
            -
             | 
| 439 | 
            +
             | 
| 412 440 | 
             
              def select_one(sql, klass)
         | 
| 413 441 | 
             
                sql = "SELECT * FROM #{klass.table} " + sql unless sql =~ /SELECT/
         | 
| 414 442 | 
             
                read_one(query(sql), klass)
         | 
| @@ -416,7 +444,7 @@ class SqlStore < Store | |
| 416 444 | 
             
              alias_method :find_by_sql_one, :select_one
         | 
| 417 445 |  | 
| 418 446 | 
             
              # Perform an aggregation over query results.
         | 
| 419 | 
            -
             | 
| 447 | 
            +
             | 
| 420 448 | 
             
              def aggregate(options)
         | 
| 421 449 | 
             
                if options.is_a?(String)
         | 
| 422 450 | 
             
                  sql = options
         | 
| @@ -425,16 +453,20 @@ class SqlStore < Store | |
| 425 453 | 
             
                  sql = "SELECT #{aggregate} FROM #{options[:class].table}"
         | 
| 426 454 | 
             
                  if condition = options[:condition]
         | 
| 427 455 | 
             
                    sql << " WHERE #{condition}"
         | 
| 456 | 
            +
                    sql << " AND " if options[:class].schema_inheritance_child?
         | 
| 457 | 
            +
                  else
         | 
| 458 | 
            +
                    sql << " WHERE " if options[:class].schema_inheritance_child?
         | 
| 428 459 | 
             
                  end
         | 
| 429 | 
            -
             | 
| 430 | 
            -
                
         | 
| 460 | 
            +
                  sql << "ogtype='#{options[:class]}'" if options[:class].schema_inheritance_child?
         | 
| 461 | 
            +
                end
         | 
| 462 | 
            +
             | 
| 431 463 | 
             
                query(sql).first_value.to_i
         | 
| 432 464 | 
             
              end
         | 
| 433 465 | 
             
              alias_method :count, :aggregate
         | 
| 434 | 
            -
             | 
| 466 | 
            +
             | 
| 435 467 | 
             
              # Relate two objects through an intermediate join table.
         | 
| 436 468 | 
             
              # Typically used in joins_many and many_to_many relations.
         | 
| 437 | 
            -
             | 
| 469 | 
            +
             | 
| 438 470 | 
             
              def join(obj1, obj2, table, options = nil)
         | 
| 439 471 | 
             
                first, second = join_object_ordering(obj1, obj2)
         | 
| 440 472 | 
             
                first_key, second_key = ordered_join_table_keys(obj1.class, obj2.class)
         | 
| @@ -443,48 +475,48 @@ class SqlStore < Store | |
| 443 475 | 
             
                else
         | 
| 444 476 | 
             
                  exec "INSERT INTO #{table} (#{first_key},#{second_key}) VALUES (#{first.pk}, #{second.pk})"
         | 
| 445 477 | 
             
                end
         | 
| 446 | 
            -
              end | 
| 478 | 
            +
              end
         | 
| 447 479 |  | 
| 448 480 | 
             
              # Unrelate two objects be removing their relation from the
         | 
| 449 481 | 
             
              # join table.
         | 
| 450 | 
            -
             | 
| 482 | 
            +
             | 
| 451 483 | 
             
              def unjoin(obj1, obj2, table)
         | 
| 452 484 | 
             
                first, second = join_object_ordering(obj1, obj2)
         | 
| 453 485 | 
             
                first_key, second_key = ordered_join_table_keys(obj1.class, obj2.class)
         | 
| 454 486 | 
             
                exec "DELETE FROM #{table} WHERE #{first_key}=#{first.pk} AND #{second_key}=#{second.pk}"    
         | 
| 455 487 | 
             
              end
         | 
| 456 | 
            -
             | 
| 488 | 
            +
             | 
| 457 489 | 
             
              def delete_all(klass)
         | 
| 458 490 | 
             
                exec "DELETE FROM #{klass.table}"
         | 
| 459 491 | 
             
              end
         | 
| 460 | 
            -
             | 
| 492 | 
            +
             | 
| 461 493 | 
             
              # :section: Transaction methods.
         | 
| 462 | 
            -
             | 
| 494 | 
            +
             | 
| 463 495 | 
             
              # Start a new transaction.
         | 
| 464 | 
            -
             | 
| 496 | 
            +
             | 
| 465 497 | 
             
              def start  
         | 
| 466 498 | 
             
                exec('START TRANSACTION') if @transaction_nesting < 1
         | 
| 467 499 | 
             
                @transaction_nesting += 1
         | 
| 468 500 | 
             
              end
         | 
| 469 | 
            -
             | 
| 501 | 
            +
             | 
| 470 502 | 
             
              # Commit a transaction.
         | 
| 471 | 
            -
             | 
| 503 | 
            +
             | 
| 472 504 | 
             
              def commit
         | 
| 473 505 | 
             
                @transaction_nesting -= 1
         | 
| 474 506 | 
             
                exec('COMMIT') if @transaction_nesting < 1
         | 
| 475 507 | 
             
              end
         | 
| 476 | 
            -
             | 
| 508 | 
            +
             | 
| 477 509 | 
             
              # Rollback a transaction.
         | 
| 478 | 
            -
             | 
| 510 | 
            +
             | 
| 479 511 | 
             
              def rollback
         | 
| 480 512 | 
             
                @transaction_nesting -= 1
         | 
| 481 513 | 
             
                exec('ROLLBACK') if @transaction_nesting < 1
         | 
| 482 514 | 
             
              end
         | 
| 483 515 |  | 
| 484 516 | 
             
              # :section: Low level methods.
         | 
| 485 | 
            -
             | 
| 517 | 
            +
             | 
| 486 518 | 
             
              # Encapsulates a low level update method.
         | 
| 487 | 
            -
             | 
| 519 | 
            +
             | 
| 488 520 | 
             
              def sql_update(sql)
         | 
| 489 521 | 
             
                exec(sql)
         | 
| 490 522 | 
             
                # return affected rows.
         | 
| @@ -503,6 +535,13 @@ private | |
| 503 535 | 
             
              # persisted.
         | 
| 504 536 |  | 
| 505 537 | 
             
              def drop_table(klass)
         | 
| 538 | 
            +
                # Remove leftover data from some join tabkes.
         | 
| 539 | 
            +
                klass.relations.each do |rel|
         | 
| 540 | 
            +
                  if rel.class.to_s == "Og::JoinsMany" and rel.join_table
         | 
| 541 | 
            +
                    target_class =  rel.target_class
         | 
| 542 | 
            +
                    exec "DELETE FROM #{rel.join_table}"
         | 
| 543 | 
            +
                  end
         | 
| 544 | 
            +
                end
         | 
| 506 545 | 
             
                exec "DROP TABLE #{klass.table}"
         | 
| 507 546 | 
             
              end
         | 
| 508 547 | 
             
              alias_method :destroy, :drop_table
         | 
| @@ -632,7 +671,7 @@ private | |
| 632 671 | 
             
                props = klass.properties.values.dup
         | 
| 633 672 | 
             
                values = props.collect { |p| write_prop(p) }.join(',') 
         | 
| 634 673 |  | 
| 635 | 
            -
                if klass.ann. | 
| 674 | 
            +
                if klass.ann.self[:superclass] or klass.ann.self[:subclasses]
         | 
| 636 675 | 
             
                  props << Property.new(:symbol => :ogtype, :klass => String)
         | 
| 637 676 | 
             
                  values << ", '#{klass}'"
         | 
| 638 677 | 
             
                end
         | 
| @@ -641,9 +680,9 @@ private | |
| 641 680 |  | 
| 642 681 | 
             
                klass.module_eval %{ 
         | 
| 643 682 | 
             
                  def og_insert(store)
         | 
| 644 | 
            -
                    #{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
         | 
| 683 | 
            +
                    #{Glue::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
         | 
| 645 684 | 
             
                    store.exec "#{sql}"
         | 
| 646 | 
            -
                    #{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
         | 
| 685 | 
            +
                    #{Glue::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
         | 
| 647 686 | 
             
                  end
         | 
| 648 687 | 
             
                }
         | 
| 649 688 | 
             
              end
         | 
| @@ -662,11 +701,11 @@ private | |
| 662 701 |  | 
| 663 702 | 
             
                klass.module_eval %{
         | 
| 664 703 | 
             
                  def og_update(store, options = nil)
         | 
| 665 | 
            -
                    #{Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
         | 
| 704 | 
            +
                    #{Glue::Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
         | 
| 666 705 | 
             
                    sql = "#{sql}"
         | 
| 667 706 | 
             
                    sql << " AND \#{options[:condition]}" if options and options[:condition]
         | 
| 668 707 | 
             
                    changed = store.sql_update(sql)
         | 
| 669 | 
            -
                    #{Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
         | 
| 708 | 
            +
                    #{Glue::Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
         | 
| 670 709 | 
             
                    return changed
         | 
| 671 710 | 
             
                  end
         | 
| 672 711 | 
             
                }
         | 
| @@ -684,57 +723,57 @@ private | |
| 684 723 |  | 
| 685 724 | 
             
                for p in props
         | 
| 686 725 | 
             
                  f = field_for_property(p).to_sym
         | 
| 687 | 
            -
             | 
| 726 | 
            +
             | 
| 688 727 | 
             
                  if col = field_map[f]
         | 
| 689 728 | 
             
                    code << "@#{p} = #{read_prop(p, col)}"
         | 
| 690 729 | 
             
                  end
         | 
| 691 730 | 
             
                end
         | 
| 692 | 
            -
             | 
| 731 | 
            +
             | 
| 693 732 | 
             
                code = code.join('; ')
         | 
| 694 733 |  | 
| 695 734 | 
             
                klass.module_eval %{
         | 
| 696 735 | 
             
                   def og_read(res, row = 0, offset = 0)
         | 
| 697 | 
            -
                    #{Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)}
         | 
| 736 | 
            +
                    #{Glue::Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)}
         | 
| 698 737 | 
             
                    #{code}
         | 
| 699 | 
            -
                    #{Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)}
         | 
| 738 | 
            +
                    #{Glue::Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)}
         | 
| 700 739 | 
             
                  end
         | 
| 701 740 | 
             
                }
         | 
| 702 | 
            -
              end | 
| 741 | 
            +
              end
         | 
| 703 742 |  | 
| 704 743 | 
             
              #--
         | 
| 705 744 | 
             
              # FIXME: is pk needed as parameter?
         | 
| 706 745 | 
             
              #++
         | 
| 707 | 
            -
             | 
| 746 | 
            +
             | 
| 708 747 | 
             
              def eval_og_delete(klass)
         | 
| 709 748 | 
             
                klass.module_eval %{
         | 
| 710 749 | 
             
                   def og_delete(store, pk, cascade = true)
         | 
| 711 | 
            -
                    #{Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)}
         | 
| 750 | 
            +
                    #{Glue::Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)}
         | 
| 712 751 | 
             
                    pk ||= @#{klass.pk_symbol}
         | 
| 713 752 | 
             
                    transaction do |tx|
         | 
| 714 753 | 
             
                      tx.exec "DELETE FROM #{klass.table} WHERE #{klass.pk_symbol}=\#{pk}"
         | 
| 715 | 
            -
                      if cascade and #{klass}.ann. | 
| 716 | 
            -
                        #{klass}.ann. | 
| 754 | 
            +
                      if cascade and #{klass}.ann.self[:descendants]
         | 
| 755 | 
            +
                        #{klass}.ann.self.descendants.each do |dclass, foreign_key|
         | 
| 717 756 | 
             
                          tx.exec "DELETE FROM \#{dclass::OGTABLE} WHERE \#{foreign_key}=\#{pk}"
         | 
| 718 757 | 
             
                        end
         | 
| 719 758 | 
             
                      end
         | 
| 720 759 | 
             
                    end
         | 
| 721 | 
            -
                    #{Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)}
         | 
| 760 | 
            +
                    #{Glue::Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)}
         | 
| 722 761 | 
             
                  end
         | 
| 723 | 
            -
                } | 
| 762 | 
            +
                }
         | 
| 724 763 | 
             
              end
         | 
| 725 764 |  | 
| 726 765 | 
             
              # Creates the schema for this class. Can be intercepted with
         | 
| 727 766 | 
             
              # aspects to add special behaviours.
         | 
| 728 | 
            -
             | 
| 767 | 
            +
             | 
| 729 768 | 
             
              def eval_og_create_schema(klass)
         | 
| 730 769 | 
             
                klass.module_eval %{
         | 
| 731 770 | 
             
                  def og_create_schema(store)
         | 
| 732 771 | 
             
                    if Og.create_schema
         | 
| 733 | 
            -
                      #{Aspects.gen_advice_code(:og_create_schema, klass.advices, :pre) if klass.respond_to?(:advices)}
         | 
| 772 | 
            +
                      #{Glue::Aspects.gen_advice_code(:og_create_schema, klass.advices, :pre) if klass.respond_to?(:advices)}
         | 
| 734 773 | 
             
                      unless self.class.superclass.ancestors.include? SchemaInheritanceBase
         | 
| 735 774 | 
             
                        store.send(:create_table, #{klass})
         | 
| 736 775 | 
             
                      end
         | 
| 737 | 
            -
                      #{Aspects.gen_advice_code(:og_create_schema, klass.advices, :post) if klass.respond_to?(:advices)}
         | 
| 776 | 
            +
                      #{Glue::Aspects.gen_advice_code(:og_create_schema, klass.advices, :post) if klass.respond_to?(:advices)}
         | 
| 738 777 | 
             
                    end
         | 
| 739 778 | 
             
                  end
         | 
| 740 779 | 
             
                }
         | 
| @@ -758,7 +797,7 @@ private | |
| 758 797 | 
             
                  }
         | 
| 759 798 | 
             
                end
         | 
| 760 799 | 
             
              end
         | 
| 761 | 
            -
             | 
| 800 | 
            +
             | 
| 762 801 | 
             
              # :section: Misc methods.
         | 
| 763 802 |  | 
| 764 803 | 
             
              def handle_sql_exception(ex, sql = nil)
         | 
| @@ -770,12 +809,26 @@ private | |
| 770 809 | 
             
                return nil 
         | 
| 771 810 | 
             
              end
         | 
| 772 811 |  | 
| 812 | 
            +
              # Resolve the finder options. Also takes scope into account.
         | 
| 813 | 
            +
              #--
         | 
| 814 | 
            +
              # FIXME: cleanup/refactor.
         | 
| 815 | 
            +
              #++
         | 
| 816 | 
            +
              
         | 
| 773 817 | 
             
              def resolve_options(klass, options)
         | 
| 818 | 
            +
                # Factor in scope.
         | 
| 819 | 
            +
                
         | 
| 820 | 
            +
                if scope = klass.get_scope
         | 
| 821 | 
            +
                  scope = scope.dup
         | 
| 822 | 
            +
                  scond = scope.delete(:condition)
         | 
| 823 | 
            +
                  scope.update(options)
         | 
| 824 | 
            +
                  options = scope
         | 
| 825 | 
            +
                end
         | 
| 826 | 
            +
              
         | 
| 774 827 | 
             
                if sql = options[:sql]
         | 
| 775 828 | 
             
                  sql = "SELECT * FROM #{klass.table} " + sql unless sql =~ /SELECT/
         | 
| 776 829 | 
             
                  return sql
         | 
| 777 830 | 
             
                end
         | 
| 778 | 
            -
             | 
| 831 | 
            +
             | 
| 779 832 | 
             
                tables = [klass::OGTABLE]
         | 
| 780 833 |  | 
| 781 834 | 
             
                if included = options[:include]
         | 
| @@ -785,7 +838,7 @@ private | |
| 785 838 | 
             
                    if rel = klass.relation(name)
         | 
| 786 839 | 
             
                      target_table = rel[:target_class]::OGTABLE
         | 
| 787 840 | 
             
                      tables << target_table
         | 
| 788 | 
            -
             | 
| 841 | 
            +
             | 
| 789 842 | 
             
                      if rel.is_a?(JoinsMany)
         | 
| 790 843 | 
             
                        tables << rel[:join_table]
         | 
| 791 844 | 
             
                        owner_key, target_key = klass.ogmanager.store.join_table_keys(klass, rel[:target_class])
         | 
| @@ -813,16 +866,21 @@ private | |
| 813 866 | 
             
                  update_condition options, options[:join_condition]
         | 
| 814 867 | 
             
                end
         | 
| 815 868 |  | 
| 816 | 
            -
                 | 
| 869 | 
            +
                # Factor in scope in the conditions.
         | 
| 870 | 
            +
                
         | 
| 871 | 
            +
                update_condition(options, scond) if scond
         | 
| 872 | 
            +
             | 
| 873 | 
            +
                # rp: type is not set in all instances such as Class.first so this fix goes here for now.
         | 
| 874 | 
            +
                if ogtype = options[:type] || (klass.schema_inheritance_child? ? "#{klass}" : nil)
         | 
| 817 875 | 
             
                  update_condition options, "ogtype='#{ogtype}'"
         | 
| 818 876 | 
             
                end
         | 
| 819 877 |  | 
| 820 878 | 
             
                sql = "SELECT #{fields} FROM #{tables.join(',')}"
         | 
| 821 | 
            -
             | 
| 879 | 
            +
             | 
| 822 880 | 
             
                if condition = options[:condition] || options[:where]
         | 
| 823 881 | 
             
                  sql << " WHERE #{condition}"
         | 
| 824 882 | 
             
                end
         | 
| 825 | 
            -
             | 
| 883 | 
            +
             | 
| 826 884 | 
             
                if order = options[:order]
         | 
| 827 885 | 
             
                  sql << " ORDER BY #{order}"
         | 
| 828 886 | 
             
                end
         | 
| @@ -831,10 +889,10 @@ private | |
| 831 889 |  | 
| 832 890 | 
             
                if extra = options[:extra]
         | 
| 833 891 | 
             
                  sql << " #{extra}"
         | 
| 834 | 
            -
                end | 
| 835 | 
            -
             | 
| 892 | 
            +
                end
         | 
| 893 | 
            +
             | 
| 836 894 | 
             
                return sql
         | 
| 837 | 
            -
              end | 
| 895 | 
            +
              end
         | 
| 838 896 |  | 
| 839 897 | 
             
              # Subclasses can override this if they need some other order.
         | 
| 840 898 | 
             
              # This is needed because different backends require different
         | 
| @@ -853,15 +911,15 @@ private | |
| 853 911 | 
             
              # :section: Deserialization methods.
         | 
| 854 912 |  | 
| 855 913 | 
             
              # Read a field (column) from a result set row.
         | 
| 856 | 
            -
             | 
| 914 | 
            +
             | 
| 857 915 | 
             
              def read_field
         | 
| 858 916 | 
             
              end
         | 
| 859 | 
            -
             | 
| 917 | 
            +
             | 
| 860 918 | 
             
              # Dynamicaly deserializes a result set row into an object.
         | 
| 861 919 | 
             
              # Used for specialized queries or join queries. Please
         | 
| 862 920 | 
             
              # not that this deserialization method is slower than the
         | 
| 863 921 | 
             
              # precompiled og_read method.
         | 
| 864 | 
            -
             | 
| 922 | 
            +
             | 
| 865 923 | 
             
              def read_row(obj, res, res_row, row)
         | 
| 866 924 | 
             
                res.fields.each_with_index do |field, idx|
         | 
| 867 925 | 
             
                  obj.instance_variable_set "@#{field}", res_row[idx]
         | 
| @@ -869,10 +927,10 @@ private | |
| 869 927 | 
             
              end
         | 
| 870 928 |  | 
| 871 929 | 
             
              # Deserialize the join relations.
         | 
| 872 | 
            -
             | 
| 930 | 
            +
             | 
| 873 931 | 
             
              def read_join_relations(obj, res_row, row, join_relations)
         | 
| 874 932 | 
             
                offset = obj.class.properties.size 
         | 
| 875 | 
            -
             | 
| 933 | 
            +
             | 
| 876 934 | 
             
                for rel in join_relations 
         | 
| 877 935 | 
             
                  rel_obj = rel[:target_class].og_allocate(res_row, row)
         | 
| 878 936 | 
             
                  rel_obj.og_read(res_row, row, offset) 
         | 
| @@ -882,7 +940,7 @@ private | |
| 882 940 | 
             
              end
         | 
| 883 941 |  | 
| 884 942 | 
             
              # Deserialize one object from the ResultSet.
         | 
| 885 | 
            -
             | 
| 943 | 
            +
             | 
| 886 944 | 
             
              def read_one(res, klass, options = nil)
         | 
| 887 945 | 
             
                return nil if res.blank?
         | 
| 888 946 |  | 
| @@ -891,11 +949,12 @@ private | |
| 891 949 | 
             
                    klass.relation(n) 
         | 
| 892 950 | 
             
                  end
         | 
| 893 951 | 
             
                end
         | 
| 894 | 
            -
             | 
| 952 | 
            +
             | 
| 895 953 | 
             
                res_row = res.next
         | 
| 896 | 
            -
                
         | 
| 954 | 
            +
                # causes STI classes to come back as the correct child class if accessed from the superclass
         | 
| 955 | 
            +
                klass = Og::Entity::entity_from_string(res_row.result.flatten[res_row.fieldnum('ogtype')]) if klass.schema_inheritance?
         | 
| 897 956 | 
             
                obj = klass.og_allocate(res_row, 0)
         | 
| 898 | 
            -
             | 
| 957 | 
            +
             | 
| 899 958 | 
             
                if options and options[:select]
         | 
| 900 959 | 
             
                  read_row(obj, res, res_row, 0)
         | 
| 901 960 | 
             
                else
         | 
| @@ -904,7 +963,7 @@ private | |
| 904 963 | 
             
                end
         | 
| 905 964 |  | 
| 906 965 | 
             
                return obj
         | 
| 907 | 
            -
             | 
| 966 | 
            +
             | 
| 908 967 | 
             
              ensure
         | 
| 909 968 | 
             
                res.close
         | 
| 910 969 | 
             
              end
         | 
| @@ -936,10 +995,10 @@ private | |
| 936 995 | 
             
                    objects << obj
         | 
| 937 996 | 
             
                  end
         | 
| 938 997 | 
             
                end
         | 
| 939 | 
            -
             | 
| 998 | 
            +
             | 
| 940 999 | 
             
                return objects
         | 
| 941 | 
            -
             | 
| 942 | 
            -
              ensure | 
| 1000 | 
            +
             | 
| 1001 | 
            +
              ensure
         | 
| 943 1002 | 
             
                res.close
         | 
| 944 1003 | 
             
              end
         | 
| 945 1004 |  | 
| @@ -962,3 +1021,4 @@ end | |
| 962 1021 | 
             
            # * Ghislain Mary
         | 
| 963 1022 | 
             
            # * Ysabel <deb@ysabel.org>
         | 
| 964 1023 | 
             
            # * Guillaume Pierronnet <guillaume.pierronnet@laposte.net>
         | 
| 1024 | 
            +
            # * Rob Pitt <rob@motionpath.com>
         |