brick 1.0.68 → 1.0.70

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 977737d8dec8900c82e2a973060dca56ae7897e281557e43e1c0696012521270
4
- data.tar.gz: 8a005ecab292cbfdaf94b29476d4db56e9f5b71a46a5f71ca6fd049550ed4cff
3
+ metadata.gz: af28a866a39817e9daa3a54b8f091c790d9280571cec94f9a3a317606e239fd9
4
+ data.tar.gz: a2cfe0b823c8b364c8f842852e631a48efe7f9dde8d506a27fbc85ebb74e58eb
5
5
  SHA512:
6
- metadata.gz: c3cfaf5cdcdf67038ba5969306f49b73c09977b9e1dec078ad3ca3d6f6e7d16b5b0d18eb16a982568966ac91cabdcb96e82362cce4906485581b36ce2e2410da
7
- data.tar.gz: 50f38455e43528478fe694f08bfcc50a8968536c9e4e6d8ab0c1713ff621e2153454a541d8e5940f584f2bd9e06549c4d3db047ac11fbdedaf5bc536d0ac7cd1
6
+ metadata.gz: 0cc18926c7c449e7dfba8d919d41b2490cb4d4a14692073f98f96903da075a31ff029f7a5f5931a845c7caf8f00b51cdba9ab0711dfa626d9e445d9122350b2b
7
+ data.tar.gz: 84373ca68edb9ebb88b8ccf2963bbf2e416c35c41840df1c1cd6922ef757cac204453aeb1e15881316681558a53ce17e32692e16320bb1b53c608bc41401920d
@@ -266,18 +266,27 @@ module ActiveRecord
266
266
  quoted_table_name = table_name.split('.').map { |x| "\"#{x}\"" }.join('.')
267
267
  order_by_txt = [] if is_do_txt
268
268
  ordering = [ordering] if ordering && !ordering.is_a?(Array)
269
- order_by = ordering&.map do |ord_part| # %%% If a term is also used as an eqi-condition in the WHERE clause, it can be omitted from ORDER BY
269
+ order_by = ordering&.each_with_object([]) do |ord_part, s| # %%% If a term is also used as an eqi-condition in the WHERE clause, it can be omitted from ORDER BY
270
270
  case ord_part
271
271
  when String
272
272
  ord_expr = ord_part.gsub('^^^', quoted_table_name)
273
273
  order_by_txt&.<<("Arel.sql(#{ord_expr})")
274
- Arel.sql(ord_expr)
274
+ s << Arel.sql(ord_expr)
275
275
  else # Expecting only Symbol
276
- ord_part = "_br_#{ord_part}_ct" if _br_hm_counts.key?(ord_part)
277
- # Retain any reference to a bt_descrip as being a symbol
278
- # Was: "#{quoted_table_name}.\"#{ord_part}\""
279
- order_by_txt&.<<(_br_bt_descrip.key?(ord_part) ? ord_part : ord_part.inspect)
280
- ord_part
276
+ if _br_hm_counts.key?(ord_part)
277
+ ord_part = "\"b_r_#{ord_part}_ct\""
278
+ elsif !_br_bt_descrip.key?(ord_part) && !column_names.include?(ord_part.to_s)
279
+ # Disallow ordering by a bogus column
280
+ # %%% Note this bogus entry so that Javascript can remove any bogus _brick_order
281
+ # parameter from the querystring, pushing it into the browser history.
282
+ ord_part = nil
283
+ end
284
+ if ord_part
285
+ # Retain any reference to a bt_descrip as being a symbol
286
+ # Was: "#{quoted_table_name}.\"#{ord_part}\""
287
+ order_by_txt&.<<(_br_bt_descrip.key?(ord_part) ? ord_part : ord_part.inspect)
288
+ s << ord_part
289
+ end
281
290
  end
282
291
  end
283
292
  [order_by, order_by_txt]
@@ -373,7 +382,9 @@ module ActiveRecord
373
382
  end
374
383
 
375
384
  def brick_select(params, selects = nil, order_by = nil, translations = {}, join_array = ::Brick::JoinArray.new)
385
+ is_postgres = ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
376
386
  is_mysql = ActiveRecord::Base.connection.adapter_name == 'Mysql2'
387
+ is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer'
377
388
  is_distinct = nil
378
389
  wheres = {}
379
390
  params.each do |k, v|
@@ -398,13 +409,15 @@ module ActiveRecord
398
409
  if selects&.empty? # Default to all columns
399
410
  tbl_no_schema = table.name.split('.').last
400
411
  columns.each do |col|
401
- col_alias = " AS _#{col.name}" if (col_name = col.name) == 'class'
412
+ col_alias = " AS #{col.name}_" if (col_name = col.name) == 'class'
402
413
  selects << if is_mysql
403
414
  "`#{tbl_no_schema}`.`#{col_name}`#{col_alias}"
404
- else
415
+ elsif is_postgres || is_mssql
405
416
  # Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
406
417
  cast_as_text = '::text' if is_distinct && Brick.relations[klass.table_name]&.[](:cols)&.[](col.name)&.first&.start_with?('xml')
407
418
  "\"#{tbl_no_schema}\".\"#{col_name}\"#{cast_as_text}#{col_alias}"
419
+ else # Sqlite or Oracle
420
+ "#{tbl_no_schema}.#{col_name}#{col_alias}"
408
421
  end
409
422
  end
410
423
  end
@@ -430,13 +443,17 @@ module ActiveRecord
430
443
  # Postgres can not use DISTINCT with any columns that are XML, so for any of those just convert to text
431
444
  is_xml = is_distinct && Brick.relations[sel_col.first.table_name]&.[](:cols)&.[](sel_col.last)&.first&.start_with?('xml')
432
445
  # If it's not unique then also include the belongs_to association name before the column name
433
- if used_col_aliases.key?(col_alias = "_brfk_#{v.first}__#{sel_col.last}")
434
- col_alias = "_brfk_#{v.first}__#{v1[idx][-2..-1].map(&:to_s).join('__')}"
446
+ if used_col_aliases.key?(col_alias = "br_fk_#{v.first}__#{sel_col.last}")
447
+ col_alias = "br_fk_#{v.first}__#{v1[idx][-2..-1].map(&:to_s).join('__')}"
435
448
  end
436
449
  selects << if is_mysql
437
450
  "`#{field_tbl_name}`.`#{sel_col.last}` AS `#{col_alias}`"
438
- else
451
+ elsif is_postgres
439
452
  "\"#{field_tbl_name}\".\"#{sel_col.last}\"#{'::text' if is_xml} AS \"#{col_alias}\""
453
+ elsif is_mssql
454
+ "\"#{field_tbl_name}\".\"#{sel_col.last}\" AS \"#{col_alias}\""
455
+ else
456
+ "#{field_tbl_name}.#{sel_col.last} AS \"#{col_alias}\""
440
457
  end
441
458
  used_col_aliases[col_alias] = nil
442
459
  v1[idx] << col_alias
@@ -447,9 +464,11 @@ module ActiveRecord
447
464
  ((id_col = k1.primary_key).is_a?(Array) ? id_col : [id_col]).each do |id_part|
448
465
  id_for_tables[v.first] << if id_part
449
466
  selects << if is_mysql
450
- "#{"`#{tbl_name}`.`#{id_part}`"} AS `#{(id_alias = "_brfk_#{v.first}__#{id_part}")}`"
467
+ "#{"`#{tbl_name}`.`#{id_part}`"} AS `#{(id_alias = "br_fk_#{v.first}__#{id_part}")}`"
468
+ elsif is_postgres || is_mssql
469
+ "#{"\"#{tbl_name}\".\"#{id_part}\""} AS \"#{(id_alias = "br_fk_#{v.first}__#{id_part}")}\""
451
470
  else
452
- "#{"\"#{tbl_name}\".\"#{id_part}\""} AS \"#{(id_alias = "_brfk_#{v.first}__#{id_part}")}\""
471
+ "#{"#{tbl_name}.#{id_part}"} AS \"#{(id_alias = "br_fk_#{v.first}__#{id_part}")}\""
453
472
  end
454
473
  id_alias
455
474
  end
@@ -479,9 +498,22 @@ module ActiveRecord
479
498
  end
480
499
  next unless count_column # %%% Would be able to remove this when multiple foreign keys to same destination becomes bulletproof
481
500
 
482
- tbl_alias = is_mysql ? "`_br_#{hm.name}`" : "\"_br_#{hm.name}\""
501
+ tbl_alias = if is_mysql
502
+ "`b_r_#{hm.name}`"
503
+ elsif is_postgres
504
+ "\"b_r_#{hm.name}\""
505
+ else
506
+ "b_r_#{hm.name}"
507
+ end
483
508
  pri_tbl = hm.active_record
484
509
  pri_tbl_name = is_mysql ? "`#{pri_tbl.table_name}`" : "\"#{pri_tbl.table_name.gsub('.', '"."')}\""
510
+ pri_tbl_name = if is_mysql
511
+ "`#{pri_tbl.table_name}`"
512
+ elsif is_postgres || is_mssql
513
+ "\"#{pri_tbl.table_name.gsub('.', '"."')}\""
514
+ else
515
+ pri_tbl.table_name
516
+ end
485
517
  on_clause = []
486
518
  if fk_col.is_a?(Array) # Composite key?
487
519
  fk_col.each_with_index { |fk_col_part, idx| on_clause << "#{tbl_alias}.#{fk_col_part} = #{pri_tbl_name}.#{pri_tbl.primary_key[idx]}" }
@@ -494,22 +526,29 @@ module ActiveRecord
494
526
  selects << poly_type
495
527
  on_clause << "#{tbl_alias}.#{poly_type} = '#{name}'"
496
528
  end
497
- hm_table_name = is_mysql ? "`#{associative&.table_name || hm.klass.table_name}`" : "\"#{(associative&.table_name || hm.klass.table_name).gsub('.', '"."')}\""
529
+ hm_table_name = if is_mysql
530
+ "`#{associative&.table_name || hm.klass.table_name}`"
531
+ elsif is_postgres || is_mssql
532
+ "\"#{(associative&.table_name || hm.klass.table_name).gsub('.', '"."')}\""
533
+ else
534
+ associative&.table_name || hm.klass.table_name
535
+ end
536
+ group_bys = ::Brick.is_oracle || is_mssql ? selects : (1..selects.length).to_a
498
537
  join_clause = "LEFT OUTER
499
538
  JOIN (SELECT #{selects.join(', ')}, COUNT(#{'DISTINCT ' if hm.options[:through]}#{count_column
500
- }) AS _ct_ FROM #{hm_table_name} GROUP BY #{(1..selects.length).to_a.join(', ')}) AS #{tbl_alias}"
539
+ }) AS c_t_ FROM #{hm_table_name} GROUP BY #{group_bys.join(', ')}) #{tbl_alias}"
501
540
  joins!("#{join_clause} ON #{on_clause.join(' AND ')}")
502
541
  end
503
542
  where!(wheres) unless wheres.empty?
504
543
  # Must parse the order_by and see if there are any symbols which refer to BT associations
505
- # as they must be expanded to find the corresponding _br_model__column naming for each.
544
+ # as they must be expanded to find the corresponding b_r_model__column naming for each.
506
545
  if order_by.present?
507
546
  final_order_by = *order_by.each_with_object([]) do |v, s|
508
547
  if v.is_a?(Symbol)
509
548
  # Add the ordered series of columns derived from the BT based on its DSL
510
549
  if (bt_cols = klass._br_bt_descrip[v])
511
550
  bt_cols.values.each do |v1|
512
- v1.each { |v2| s << v2.last if v2.length > 1 }
551
+ v1.each { |v2| s << "\"#{v2.last}\"" if v2.length > 1 }
513
552
  end
514
553
  else
515
554
  s << v
@@ -625,7 +664,7 @@ Module.class_exec do
625
664
  )
626
665
  return possible
627
666
  end
628
- class_name = args.first.to_s
667
+ class_name = ::Brick.namify(args.first.to_s)
629
668
  # self.name is nil when a model name is requested in an .erb file
630
669
  base_module = (self < ActiveRecord::Migration || !self.name) ? Object : self
631
670
  # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
@@ -737,7 +776,7 @@ class Object
737
776
  schema_name = Apartment.default_schema
738
777
  end
739
778
  # Maybe, just maybe there's a database table that will satisfy this need
740
- if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(schema_name ? "#{schema_name}.#{m}" : m) })
779
+ if (matching = [table_name, singular_table_name, plural_class_name, model_name, table_name.titleize].find { |m| relations.key?(schema_name ? "#{schema_name}.#{m}" : m) })
741
780
  build_model_worker(schema_name, inheritable_name, model_name, singular_table_name, table_name, relations, matching)
742
781
  end
743
782
  end
@@ -1020,6 +1059,7 @@ class Object
1020
1059
  table_name = ActiveSupport::Inflector.underscore(plural_class_name)
1021
1060
  singular_table_name = ActiveSupport::Inflector.singularize(table_name)
1022
1061
  pk = model&._brick_primary_key(relations.fetch(table_name, nil))
1062
+ is_postgres = ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
1023
1063
  is_mysql = ActiveRecord::Base.connection.adapter_name == 'Mysql2'
1024
1064
 
1025
1065
  namespace_name = "#{namespace.name}::" if namespace
@@ -1125,7 +1165,13 @@ class Object
1125
1165
  # %%% Add custom HM count columns
1126
1166
  # %%% What happens when the PK is composite?
1127
1167
  counts = model._br_hm_counts.each_with_object([]) do |v, s|
1128
- s << (is_mysql ? "`_br_#{v.first}`._ct_ AS \"_br_#{v.first}_ct\"" : "\"_br_#{v.first}\"._ct_ AS \"_br_#{v.first}_ct\"")
1168
+ s << if is_mysql
1169
+ "`b_r_#{v.first}`.c_t_ AS \"b_r_#{v.first}_ct\""
1170
+ elsif is_postgres
1171
+ "\"b_r_#{v.first}\".c_t_ AS \"b_r_#{v.first}_ct\""
1172
+ else
1173
+ "b_r_#{v.first}.c_t_ AS \"b_r_#{v.first}_ct\""
1174
+ end
1129
1175
  end
1130
1176
  instance_variable_set("@#{table_name}".to_sym, ar_relation.dup._select!(*selects, *counts))
1131
1177
  if namespace && (idx = lookup_context.prefixes.index(table_name))
@@ -1297,7 +1343,7 @@ class Object
1297
1343
  # hm_assoc[:assoc_name] = new_alt_name
1298
1344
  [new_alt_name, true]
1299
1345
  else
1300
- assoc_name = hm_assoc[:inverse_table].pluralize
1346
+ assoc_name = ::Brick.namify(hm_assoc[:inverse_table]).pluralize
1301
1347
  # hm_assoc[:assoc_name] = assoc_name
1302
1348
  [assoc_name, assoc_name.include?('.')]
1303
1349
  end
@@ -1340,12 +1386,20 @@ module ActiveRecord::ConnectionHandling
1340
1386
  # puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
1341
1387
 
1342
1388
  is_postgres = nil
1389
+ is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer'
1343
1390
  case ActiveRecord::Base.connection.adapter_name
1344
- when 'PostgreSQL'
1345
- is_postgres = true
1391
+ when 'PostgreSQL', 'SQLServer'
1392
+ is_postgres = !is_mssql
1346
1393
  db_schemas = ActiveRecord::Base.execute_sql('SELECT DISTINCT table_schema FROM INFORMATION_SCHEMA.tables;')
1347
1394
  ::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
1348
- row = row.is_a?(String) ? row : row['table_schema']
1395
+ row = case row
1396
+ when String
1397
+ row
1398
+ when Array
1399
+ row.first
1400
+ else
1401
+ row['table_schema']
1402
+ end
1349
1403
  # Remove any system schemas
1350
1404
  s[row] = nil unless ['information_schema', 'pg_catalog'].include?(row)
1351
1405
  end
@@ -1357,6 +1411,11 @@ module ActiveRecord::ConnectionHandling
1357
1411
  end
1358
1412
  when 'Mysql2'
1359
1413
  ::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
1414
+ when 'OracleEnhanced'
1415
+ # ActiveRecord::Base.connection.current_database will be something like "XEPDB1"
1416
+ ::Brick.default_schema = schema = ActiveRecord::Base.connection.raw_connection.username
1417
+ ::Brick.db_schemas = {}
1418
+ execute_oracle("SELECT username FROM sys.all_users WHERE ORACLE_MAINTAINED != 'Y'").each { |s| ::Brick.db_schemas[s.first] = nil }
1360
1419
  when 'SQLite'
1361
1420
  sql = "SELECT m.name AS relation_name, UPPER(m.type) AS table_type,
1362
1421
  p.name AS column_name, p.type AS data_type,
@@ -1389,7 +1448,7 @@ module ActiveRecord::ConnectionHandling
1389
1448
  case ActiveRecord::Base.connection.adapter_name
1390
1449
  when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
1391
1450
  # schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
1392
- ActiveRecord::Base.retrieve_schema_and_tables(sql, is_postgres, schema).each do |r|
1451
+ ActiveRecord::Base.retrieve_schema_and_tables(sql, is_postgres, is_mssql, schema).each do |r|
1393
1452
  # If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
1394
1453
  # is the default schema, usually 'public'.
1395
1454
  schema_name = if ::Brick.config.schema_behavior[:multitenant]
@@ -1415,9 +1474,32 @@ module ActiveRecord::ConnectionHandling
1415
1474
  cols[col_name] = [r['data_type'], r['max_length'], measures&.include?(col_name), r['is_nullable'] == 'NO']
1416
1475
  # puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
1417
1476
  end
1418
- else # MySQL2 acts a little differently, bringing back an array for each row
1419
- ActiveRecord::Base.retrieve_schema_and_tables(sql).each do |r|
1420
- relation = relations[(relation_name = r[1])] # here relation represents a table or view from the database
1477
+ else # MySQL2 and OracleEnhanced act a little differently, bringing back an array for each row
1478
+ schema_and_tables = case ActiveRecord::Base.connection.adapter_name
1479
+ when 'OracleEnhanced'
1480
+ sql =
1481
+ "SELECT c.owner AS schema, c.table_name AS relation_name, 'BASE_TABLE' AS table_type,
1482
+ LOWER(c.column_name) AS column_name, c.data_type,
1483
+ COALESCE(c.data_length, c.data_precision) AS max_length,
1484
+ CASE ac.constraint_type WHEN 'P' THEN 'PRIMARY KEY' END AS const,
1485
+ ac.constraint_name AS \"key\",
1486
+ CASE c.nullable WHEN 'Y' THEN 'YES' ELSE 'NO' END AS is_nullable
1487
+ FROM all_tab_cols c
1488
+ LEFT OUTER JOIN all_cons_columns acc ON acc.owner = c.owner AND acc.table_name = c.table_name AND acc.column_name = c.column_name
1489
+ LEFT OUTER JOIN all_constraints ac ON ac.owner = acc.owner AND ac.table_name = acc.table_name AND ac.constraint_name = acc.constraint_name AND constraint_type = 'P'
1490
+ WHERE c.owner IN (#{::Brick.db_schemas.keys.map { |s| "'#{s}'" }.join(', ')})
1491
+ ORDER BY 1, 2, c.internal_column_id, acc.position"
1492
+ execute_oracle(sql, *ar_tables)
1493
+ else
1494
+ ActiveRecord::Base.retrieve_schema_and_tables(sql)
1495
+ end
1496
+ schema_and_tables.each do |r|
1497
+ next if r[1].index('$') # Oracle can have goofy table names with $
1498
+
1499
+ if (relation_name = r[1]) =~ /^[A-Z_]+$/
1500
+ relation_name.downcase!
1501
+ end
1502
+ relation = relations[relation_name] # here relation represents a table or view from the database
1421
1503
  relation[:isView] = true if r[2] == 'VIEW' # table_type
1422
1504
  col_name = r[3]
1423
1505
  key = case r[6] # constraint type
@@ -1457,9 +1539,9 @@ module ActiveRecord::ConnectionHandling
1457
1539
  # end
1458
1540
  # schema = ::Brick.default_schema # Reset back for this next round of fun
1459
1541
  case ActiveRecord::Base.connection.adapter_name
1460
- when 'PostgreSQL', 'Mysql2'
1542
+ when 'PostgreSQL', 'Mysql2', 'SQLServer'
1461
1543
  sql = "SELECT kcu1.CONSTRAINT_SCHEMA, kcu1.TABLE_NAME, kcu1.COLUMN_NAME,
1462
- kcu2.CONSTRAINT_SCHEMA AS primary_schema, kcu2.TABLE_NAME AS primary_table, kcu1.CONSTRAINT_NAME AS CONSTRAINT_SCHEMA_FK
1544
+ kcu2.CONSTRAINT_SCHEMA AS primary_schema, kcu2.TABLE_NAME AS primary_table, kcu1.CONSTRAINT_NAME AS CONSTRAINT_SCHEMA_FK
1463
1545
  FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
1464
1546
  INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
1465
1547
  ON kcu1.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
@@ -1470,36 +1552,55 @@ module ActiveRecord::ConnectionHandling
1470
1552
  AND kcu2.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA
1471
1553
  AND kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME#{"
1472
1554
  AND kcu2.TABLE_NAME = kcu1.REFERENCED_TABLE_NAME
1473
- AND kcu2.COLUMN_NAME = kcu1.REFERENCED_COLUMN_NAME" unless is_postgres }
1555
+ AND kcu2.COLUMN_NAME = kcu1.REFERENCED_COLUMN_NAME" unless is_postgres || is_mssql }
1474
1556
  AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION#{"
1475
1557
  WHERE kcu1.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }"
1476
1558
  # AND kcu2.TABLE_NAME = ?;", Apartment::Tenant.current, table_name
1559
+ fk_references = ActiveRecord::Base.execute_sql(sql)
1477
1560
  when 'SQLite'
1478
1561
  sql = "SELECT m.name, fkl.\"from\", fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
1479
1562
  FROM sqlite_master m
1480
1563
  INNER JOIN pragma_foreign_key_list(m.name) fkl ON m.type = 'table'
1481
1564
  ORDER BY m.name, fkl.seq"
1482
- else
1565
+ fk_references = ActiveRecord::Base.execute_sql(sql)
1566
+ when 'OracleEnhanced'
1567
+ schemas = ::Brick.db_schemas.keys.map { |s| "'#{s}'" }.join(', ')
1568
+ sql =
1569
+ "SELECT -- fk
1570
+ ac.owner AS constraint_schema, acc_fk.table_name, LOWER(acc_fk.column_name),
1571
+ -- referenced pk
1572
+ ac.r_owner AS primary_schema, acc_pk.table_name AS primary_table, acc_fk.constraint_name AS constraint_schema_fk
1573
+ -- , LOWER(acc_pk.column_name)
1574
+ FROM all_cons_columns acc_fk
1575
+ INNER JOIN all_constraints ac ON acc_fk.owner = ac.owner
1576
+ AND acc_fk.constraint_name = ac.constraint_name
1577
+ INNER JOIN all_cons_columns acc_pk ON ac.r_owner = acc_pk.owner
1578
+ AND ac.r_constraint_name = acc_pk.constraint_name
1579
+ WHERE ac.constraint_type = 'R'
1580
+ AND ac.owner IN (#{schemas})
1581
+ AND ac.r_owner IN (#{schemas})"
1582
+ fk_references = ActiveRecord::Base.execute_oracle(sql)
1483
1583
  end
1484
- if sql
1485
- # ::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
1486
- ActiveRecord::Base.execute_sql(sql).each do |fk|
1487
- fk = fk.values unless fk.is_a?(Array)
1488
- # Multitenancy makes things a little more general overall, except for non-tenanted tables
1489
- if apartment_excluded&.include?(fk[1].singularize.camelize)
1490
- fk[0] = Apartment.default_schema
1491
- elsif is_postgres && (fk[0] == 'public' || (is_multitenant && fk[0] == schema)) ||
1492
- !is_postgres && ['mysql', 'performance_schema', 'sys'].exclude?(fk[0])
1493
- fk[0] = nil
1494
- end
1495
- if apartment_excluded&.include?(fk[4].singularize.camelize)
1496
- fk[3] = Apartment.default_schema
1497
- elsif is_postgres && (fk[3] == 'public' || (is_multitenant && fk[3] == schema)) ||
1498
- !is_postgres && ['mysql', 'performance_schema', 'sys'].exclude?(fk[3])
1499
- fk[3] = nil
1500
- end
1501
- ::Brick._add_bt_and_hm(fk, relations)
1584
+ ::Brick.is_oracle = true if ActiveRecord::Base.connection.adapter_name == 'OracleEnhanced'
1585
+ # ::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
1586
+ fk_references&.each do |fk|
1587
+ fk = fk.values unless fk.is_a?(Array)
1588
+ # Multitenancy makes things a little more general overall, except for non-tenanted tables
1589
+ if apartment_excluded&.include?(::Brick.namify(fk[1]).singularize.camelize)
1590
+ fk[0] = Apartment.default_schema
1591
+ elsif is_postgres && (fk[0] == 'public' || (is_multitenant && fk[0] == schema)) ||
1592
+ !is_postgres && ['mysql', 'performance_schema', 'sys'].exclude?(fk[0])
1593
+ fk[0] = nil
1502
1594
  end
1595
+ if apartment_excluded&.include?(fk[4].singularize.camelize)
1596
+ fk[3] = Apartment.default_schema
1597
+ elsif is_postgres && (fk[3] == 'public' || (is_multitenant && fk[3] == schema)) ||
1598
+ !is_postgres && ['mysql', 'performance_schema', 'sys'].exclude?(fk[3])
1599
+ fk[3] = nil
1600
+ end
1601
+ fk[1].downcase! if ::Brick.is_oracle && fk[1] =~ /^[A-Z_]+$/
1602
+ fk[4].downcase! if ::Brick.is_oracle && fk[4] =~ /^[A-Z_]+$/
1603
+ ::Brick._add_bt_and_hm(fk, relations)
1503
1604
  end
1504
1605
  end
1505
1606
 
@@ -1530,29 +1631,35 @@ module ActiveRecord::ConnectionHandling
1530
1631
  ::Brick.load_additional_references if initializer_loaded
1531
1632
  end
1532
1633
 
1533
- def retrieve_schema_and_tables(sql = nil, is_postgres = nil, schema = nil)
1634
+ def retrieve_schema_and_tables(sql = nil, is_postgres = nil, is_mssql = nil, schema = nil)
1635
+ is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer' if is_mssql.nil?
1534
1636
  sql ||= "SELECT t.table_schema AS \"schema\", t.table_name AS relation_name, t.table_type,#{"
1535
1637
  pg_catalog.obj_description(
1536
1638
  ('\"' || t.table_schema || '\".\"' || t.table_name || '\"')::regclass, 'pg_class'
1537
1639
  ) AS table_description," if is_postgres}
1538
1640
  c.column_name, c.data_type,
1539
1641
  COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
1540
- tc.constraint_type AS const, kcu.constraint_name AS \"key\",
1642
+ kcu.constraint_type AS const, kcu.constraint_name AS \"key\",
1541
1643
  c.is_nullable
1542
1644
  FROM INFORMATION_SCHEMA.tables AS t
1543
1645
  LEFT OUTER JOIN INFORMATION_SCHEMA.columns AS c ON t.table_schema = c.table_schema
1544
1646
  AND t.table_name = c.table_name
1545
- LEFT OUTER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu ON
1546
- -- ON kcu.CONSTRAINT_CATALOG = t.table_catalog AND
1647
+ LEFT OUTER JOIN
1648
+ (SELECT kcu1.constraint_schema, kcu1.table_name, kcu1.ordinal_position,
1649
+ tc.constraint_type, kcu1.constraint_name
1650
+ FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
1651
+ INNER JOIN INFORMATION_SCHEMA.table_constraints AS tc
1652
+ ON kcu1.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
1653
+ AND kcu1.TABLE_NAME = tc.TABLE_NAME
1654
+ AND kcu1.CONSTRAINT_NAME = tc.constraint_name
1655
+ AND tc.constraint_type != 'FOREIGN KEY' -- For MSSQL
1656
+ ) AS kcu ON
1657
+ -- kcu.CONSTRAINT_CATALOG = t.table_catalog AND
1547
1658
  kcu.CONSTRAINT_SCHEMA = c.table_schema
1548
- AND kcu.TABLE_NAME = c.table_name
1549
- AND kcu.position_in_unique_constraint IS NULL
1659
+ AND kcu.TABLE_NAME = c.table_name#{"
1660
+ -- AND kcu.position_in_unique_constraint IS NULL" unless is_mssql}
1550
1661
  AND kcu.ordinal_position = c.ordinal_position
1551
- LEFT OUTER JOIN INFORMATION_SCHEMA.table_constraints AS tc
1552
- ON kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
1553
- AND kcu.TABLE_NAME = tc.TABLE_NAME
1554
- AND kcu.CONSTRAINT_NAME = tc.constraint_name
1555
- WHERE t.table_schema #{is_postgres ?
1662
+ WHERE t.table_schema #{is_postgres || is_mssql ?
1556
1663
  "NOT IN ('information_schema', 'pg_catalog')"
1557
1664
  :
1558
1665
  "= '#{ActiveRecord::Base.connection.current_database.tr("'", "''")}'"}#{"
@@ -1560,13 +1667,27 @@ module ActiveRecord::ConnectionHandling
1560
1667
  -- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
1561
1668
  AND t.table_name NOT IN ('pg_stat_statements', ?, ?)
1562
1669
  ORDER BY 1, t.table_type DESC, 2, c.ordinal_position"
1670
+ ActiveRecord::Base.execute_sql(sql, *ar_tables)
1671
+ end
1672
+
1673
+ def ar_tables
1563
1674
  ar_smtn = if ActiveRecord::Base.respond_to?(:schema_migrations_table_name)
1564
1675
  ActiveRecord::Base.schema_migrations_table_name
1565
1676
  else
1566
1677
  'schema_migrations'
1567
1678
  end
1568
1679
  ar_imtn = ActiveRecord.version >= ::Gem::Version.new('5.0') ? ActiveRecord::Base.internal_metadata_table_name : ''
1569
- ActiveRecord::Base.execute_sql(sql, ar_smtn, ar_imtn)
1680
+ [ar_smtn, ar_imtn]
1681
+ end
1682
+
1683
+ def execute_oracle(*sql_args)
1684
+ cursor = ActiveRecord::Base.execute_sql(*sql_args)
1685
+ result = []
1686
+ while row = cursor.fetch()
1687
+ result << row
1688
+ end
1689
+ cursor.close
1690
+ result
1570
1691
  end
1571
1692
  end
1572
1693
 
@@ -1594,22 +1715,23 @@ module Brick
1594
1715
 
1595
1716
  class << self
1596
1717
  def _add_bt_and_hm(fk, relations, is_polymorphic = false, is_optional = false)
1597
- bt_assoc_name = fk[2]
1718
+ bt_assoc_name = ::Brick.namify(fk[2])
1598
1719
  unless is_polymorphic
1599
1720
  bt_assoc_name = if bt_assoc_name.underscore.end_with?('_id')
1600
- bt_assoc_name[0..-4]
1721
+ bt_assoc_name[-3] == '_' ? bt_assoc_name[0..-4] : bt_assoc_name[0..-3]
1601
1722
  elsif bt_assoc_name.downcase.end_with?('id') && bt_assoc_name.exclude?('_')
1602
1723
  bt_assoc_name[0..-3] # Make the bold assumption that we can just peel off any final ID part
1603
1724
  else
1604
1725
  "#{bt_assoc_name}_bt"
1605
1726
  end
1606
1727
  end
1607
- bt_assoc_name = "_#{bt_assoc_name}" if bt_assoc_name == 'attribute'
1728
+ bt_assoc_name = "#{bt_assoc_name}_" if bt_assoc_name == 'attribute'
1608
1729
 
1609
1730
  # %%% Temporary schema patch
1610
1731
  for_tbl = fk[1]
1732
+ fk_namified = ::Brick.namify(fk[1])
1611
1733
  apartment = Object.const_defined?('Apartment') && Apartment
1612
- fk[0] = Apartment.default_schema if apartment && apartment.excluded_models.include?(for_tbl.singularize.camelize)
1734
+ fk[0] = Apartment.default_schema if apartment && apartment.excluded_models.include?(fk_namified.singularize.camelize)
1613
1735
  fk[1] = "#{fk[0]}.#{fk[1]}" if fk[0] # && fk[0] != ::Brick.default_schema
1614
1736
  bts = (relation = relations.fetch(fk[1], nil))&.fetch(:fks) { relation[:fks] = {} }
1615
1737
 
@@ -1708,7 +1830,7 @@ module Brick
1708
1830
  else
1709
1831
  fk[1]
1710
1832
  end
1711
- assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[2], assoc_name: for_tbl.pluralize, alternate_name: bt_assoc_name,
1833
+ assoc_hm = hms[hm_cnstr_name] = { is_bt: false, fk: fk[2], assoc_name: fk_namified.pluralize, alternate_name: bt_assoc_name,
1712
1834
  inverse_table: inv_tbl, inverse: assoc_bt }
1713
1835
  assoc_hm[:polymorphic] = true if is_polymorphic
1714
1836
  hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
@@ -49,8 +49,10 @@ module Brick
49
49
  # After we're initialized and before running the rest of stuff, put our configuration in place
50
50
  ActiveSupport.on_load(:after_initialize) do |app|
51
51
  assets_path = File.expand_path("#{__dir__}/../../../../vendor/assets")
52
- (app.config.assets.precompile ||= []) << "#{assets_path}/images/brick_erd.png"
53
- (app.config.assets.paths ||= []) << assets_path
52
+ if (app_config = app.config).respond_to?(:assets)
53
+ (app_config.assets.precompile ||= []) << "#{assets_path}/images/brick_erd.png"
54
+ (app.config.assets.paths ||= []) << assets_path
55
+ end
54
56
  # ====================================
55
57
  # Dynamically create generic templates
56
58
  # ====================================
@@ -145,7 +147,7 @@ module Brick
145
147
  'nil'
146
148
  else
147
149
  # Postgres column names are limited to 63 characters
148
- "#{obj_name}.#{"_br_#{assoc_name}_ct"[0..62]} || 0"
150
+ "#{obj_name}.#{"b_r_#{assoc_name}_ct"[0..62]} || 0"
149
151
  end
150
152
  ", #{set_ct}, #{path_keys(hm_assoc, hm_fk_name, obj_name, pk)}"
151
153
  end
@@ -565,7 +567,7 @@ erDiagram
565
567
  end %>
566
568
  <% end
567
569
  def dt_lookup(dt)
568
- { 'integer' => 'int', }[dt] || dt.tr(' ', '_')
570
+ { 'integer' => 'int', }[dt] || dt&.tr(' ', '_') || 'int'
569
571
  end
570
572
  callbacks.merge({model_short_name => #{@_brick_model.name}}).each do |cb_k, cb_class|
571
573
  cb_relation = ::Brick.relations[cb_class.table_name]
@@ -581,8 +583,9 @@ erDiagram
581
583
  fk.each do |fk_part| %>
582
584
  <%= \"#\{dt_lookup(cols[fk_part].first)} #\{fk_part} \\\"&nbsp;&nbsp;&nbsp;&nbsp;fk\\\"\".html_safe unless pkeys&.include?(fk_part) %><%
583
585
  end
584
- else %>
585
- <%= \"#\{dt_lookup(cols[fk].first)} #\{fk} \\\"&nbsp;&nbsp;&nbsp;&nbsp;fk\\\"\".html_safe unless pkeys&.include?(fk) %><%
586
+ else # %%% Does not yet accommodate polymorphic BTs
587
+ %>
588
+ <%= \"#\{dt_lookup(cols[fk]&.first)} #\{fk} \\\"&nbsp;&nbsp;&nbsp;&nbsp;fk\\\"\".html_safe unless pkeys&.include?(fk) %><%
586
589
  end
587
590
  end %>
588
591
  }
@@ -1096,7 +1099,9 @@ flatpickr(\".timepicker\", {enableTime: true, noCalendar: true});
1096
1099
  gErd.setAttribute(\"class\", \"relatedModel\");
1097
1100
  gErd.addEventListener(\"click\",
1098
1101
  function (evt) {
1099
- location.href = changeout(changeout(location.href, -1, cbs[this.id]), \"_brick_erd\", \"1\");
1102
+ location.href = changeout(changeout(
1103
+ changeout(location.href, '_brick_order', null), // Remove any ordering
1104
+ -1, cbs[this.id]), \"_brick_erd\", \"1\");
1100
1105
  }
1101
1106
  );
1102
1107
  }
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 68
8
+ TINY = 70
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
data/lib/brick.rb CHANGED
@@ -123,7 +123,7 @@ module Brick
123
123
  end
124
124
 
125
125
  class << self
126
- attr_accessor :default_schema, :db_schemas, :routes_done
126
+ attr_accessor :default_schema, :db_schemas, :routes_done, :is_oracle
127
127
 
128
128
  def set_db_schema(params)
129
129
  schema = params['_brick_schema'] || 'public'
@@ -151,6 +151,20 @@ module Brick
151
151
  @pending_models ||= {}
152
152
  end
153
153
 
154
+ # Convert spaces to underscores if the second character and onwards is mixed case
155
+ def namify(name)
156
+ if name.include?(' ')
157
+ # All uppers or all lowers?
158
+ if name[1..-1] =~ /^[A-Z0-9_]+$/ || name[1..-1] =~ /^[a-z0-9_]+$/
159
+ name.titleize.tr(' ', '_')
160
+ else # Mixed uppers and lowers -- just remove existing spaces
161
+ name.tr(' ', '')
162
+ end
163
+ else
164
+ name
165
+ end
166
+ end
167
+
154
168
  def get_bts_and_hms(model)
155
169
  bts, hms = model.reflect_on_all_associations.each_with_object([{}, {}]) do |a, s|
156
170
  next if !const_defined?(a.name.to_s.singularize.camelize) && ::Brick.config.exclude_tables.include?(a.plural_name)
@@ -469,9 +483,10 @@ In config/initializers/brick.rb appropriate entries would look something like:
469
483
  # %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
470
484
  # If auto-controllers and auto-models are both enabled then this makes sense:
471
485
  ::Brick.relations.each do |rel_name, v|
472
- rel_name = rel_name.split('.').map(&:underscore)
486
+ rel_name = rel_name.split('.').map { |x| ::Brick.namify(x).underscore }
473
487
  schema_names = rel_name[0..-2]
474
488
  schema_names.shift if ::Brick.apartment_multitenant && schema_names.first == Apartment.default_schema
489
+ # %%% If more than one schema has the same table name, will need to add a schema name prefix to have uniqueness
475
490
  k = rel_name.last
476
491
  unless existing_controllers.key?(controller_name = k.pluralize)
477
492
  options = {}
@@ -545,7 +560,11 @@ ActiveSupport.on_load(:active_record) do
545
560
  class << self
546
561
  def execute_sql(sql, *param_array)
547
562
  param_array = param_array.first if param_array.length == 1 && param_array.first.is_a?(Array)
548
- connection.execute(send(:sanitize_sql_array, [sql] + param_array))
563
+ if ActiveRecord::Base.connection.adapter_name == 'SQLServer'
564
+ connection.exec_query(send(:sanitize_sql_array, [sql] + param_array)).rows
565
+ else
566
+ connection.execute(send(:sanitize_sql_array, [sql] + param_array))
567
+ end
549
568
  end
550
569
  end
551
570
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brick
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.68
4
+ version: 1.0.70
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorin Thwaits
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-04 00:00:00.000000000 Z
11
+ date: 2022-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '4.2'
19
+ version: '3.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '4.2'
26
+ version: '3.1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: fancy_gets
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -264,5 +264,5 @@ requirements: []
264
264
  rubygems_version: 3.1.6
265
265
  signing_key:
266
266
  specification_version: 4
267
- summary: Import and Export Data
267
+ summary: Create a Rails app from data alone
268
268
  test_files: []