brick 1.0.198 → 1.0.200
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/brick/extensions.rb +115 -605
- data/lib/brick/frameworks/rails/engine.rb +2 -37
- data/lib/brick/frameworks/rails/form_tags.rb +3 -3
- data/lib/brick/reflect_tables.rb +582 -0
- data/lib/brick/route_mapper.rb +20 -4
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +2 -1
- data/lib/generators/brick/migration_builder.rb +8 -5
- metadata +3 -2
data/lib/brick/extensions.rb
CHANGED
@@ -436,7 +436,11 @@ module ActiveRecord
|
|
436
436
|
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
|
437
437
|
case ord_part
|
438
438
|
when String
|
439
|
-
ord_expr =
|
439
|
+
ord_expr = if ord_part.index('(') # Any kind of SQL function at play here?
|
440
|
+
ord_part.gsub('^^^', _br_quoted_name(table_name))
|
441
|
+
else
|
442
|
+
_br_quoted_name(ord_part.gsub('^^^', table_name))
|
443
|
+
end
|
440
444
|
s << Arel.sql(ord_expr)
|
441
445
|
order_by_txt&.<<(ord_expr.index('.') ? "Arel.sql(#{ord_expr.inspect})" : ord_part.inspect)
|
442
446
|
else # Expecting only Symbol
|
@@ -887,12 +891,16 @@ module ActiveRecord
|
|
887
891
|
|
888
892
|
tbl_alias = unique63("b_r_#{hm.name}", previous)
|
889
893
|
on_clause = []
|
890
|
-
hm_selects = if
|
891
|
-
fk_col.
|
892
|
-
|
893
|
-
|
894
|
+
hm_selects = if !pri_key.is_a?(Array) # Probable standard key?
|
895
|
+
if fk_col.is_a?(Array) # Foreign is composite but not Primary? OK, or choose the first part of the foreign key if nothing else
|
896
|
+
fk_col = fk_col.find { |col_name| col_name == pri_key } || # Try to associate with the same-named part of the foreign key ...
|
897
|
+
fk_col.first # ... and if no good match, just choose the first part
|
898
|
+
end
|
894
899
|
on_clause << "#{_br_quoted_name("#{tbl_alias}.#{fk_col}")} = #{_br_quoted_name("#{pri_tbl.table_name}.#{pri_key}")}"
|
895
900
|
[fk_col]
|
901
|
+
else # Composite key
|
902
|
+
fk_col.each_with_index { |fk_col_part, idx| on_clause << "#{tbl_alias}.#{fk_col_part} = #{pri_tbl.table_name}.#{pri_key[idx]}" }
|
903
|
+
fk_col.dup
|
896
904
|
end
|
897
905
|
if poly_type
|
898
906
|
hm_selects << poly_type
|
@@ -1166,6 +1174,34 @@ Might want to add this in your brick.rb:
|
|
1166
1174
|
end
|
1167
1175
|
end
|
1168
1176
|
|
1177
|
+
if ::Brick.enable_routes? && Object.const_defined?('ActionDispatch')
|
1178
|
+
require 'brick/route_mapper'
|
1179
|
+
ActionDispatch::Routing::RouteSet.class_exec do
|
1180
|
+
# In order to defer auto-creation of any routes that already exist, calculate Brick routes only after having loaded all others
|
1181
|
+
prepend ::Brick::RouteSet
|
1182
|
+
end
|
1183
|
+
ActionDispatch::Routing::Mapper.class_exec do
|
1184
|
+
include ::Brick::RouteMapper
|
1185
|
+
end
|
1186
|
+
|
1187
|
+
# Do the root route before the Rails Welcome one would otherwise take precedence
|
1188
|
+
if (route = ::Brick.config.default_route_fallback).present?
|
1189
|
+
action = "#{route}#{'#index' unless route.index('#')}"
|
1190
|
+
if ::Brick.config.path_prefix
|
1191
|
+
::Rails.application.routes.append do
|
1192
|
+
send(:namespace, ::Brick.config.path_prefix) do
|
1193
|
+
send(:root, action)
|
1194
|
+
end
|
1195
|
+
end
|
1196
|
+
elsif ::Rails.application.routes.named_routes.send(:routes)[:root].nil?
|
1197
|
+
::Rails.application.routes.append do
|
1198
|
+
send(:root, action)
|
1199
|
+
end
|
1200
|
+
end
|
1201
|
+
::Brick.established_drf = "/#{::Brick.config.path_prefix}#{action[action.index('#')..-1]}"
|
1202
|
+
end
|
1203
|
+
end
|
1204
|
+
|
1169
1205
|
if Object.const_defined?('ActionView')
|
1170
1206
|
require 'brick/frameworks/rails/form_tags'
|
1171
1207
|
require 'brick/frameworks/rails/form_builder'
|
@@ -1298,19 +1334,23 @@ end
|
|
1298
1334
|
unless (is_tnp_module = (is_brick_prefix && !is_controller && ::Brick.config.table_name_prefixes.values.include?(requested)))
|
1299
1335
|
# ... first look around for an existing module or class.
|
1300
1336
|
desired_classname = (self == Object || !name) ? requested : "#{name}::#{requested}"
|
1301
|
-
if (
|
1337
|
+
if (self.const_defined?(args.first) && (possible = self.const_get(args.first)) &&
|
1302
1338
|
# Reset `possible` if it's a controller request that's not a perfect match
|
1303
1339
|
# Was: (possible = nil) but changed to #local_variable_set in order to suppress the "= should be ==" warning
|
1304
1340
|
(possible&.name == desired_classname || (is_controller && binding.local_variable_set(:possible, nil)))) ||
|
1341
|
+
|
1305
1342
|
# Try to require the respective Ruby file
|
1343
|
+
# ((filename = ActiveSupport::Dependencies.search_for_file(desired_classname.underscore)) &&
|
1344
|
+
# (require_dependency(filename) || true) &&
|
1306
1345
|
((filename = ActiveSupport::Dependencies.search_for_file(desired_classname.underscore) ||
|
1307
1346
|
(self != Object && ActiveSupport::Dependencies.search_for_file((desired_classname = requested).underscore))
|
1308
1347
|
) && (require_dependency(filename) || true) &&
|
1309
1348
|
((possible = self.const_get(args.first)) && possible.name == desired_classname)
|
1310
1349
|
) ||
|
1350
|
+
|
1311
1351
|
# If any class has turned up so far (and we're not in the middle of eager loading)
|
1312
1352
|
# then return what we've found.
|
1313
|
-
(
|
1353
|
+
(possible&.module_parent == base_module && !::Brick.is_eager_loading) # Used to also have: && possible != self
|
1314
1354
|
if ((!brick_root && (filename || possible.instance_of?(Class))) ||
|
1315
1355
|
(possible.instance_of?(Module) && possible&.module_parent == self) ||
|
1316
1356
|
(possible.instance_of?(Class) && possible == self)) && # Are we simply searching for ourselves?
|
@@ -1342,13 +1382,14 @@ end
|
|
1342
1382
|
singular_class_name = ::Brick.namify(plural_class_name, :underscore).singularize.camelize
|
1343
1383
|
full_class_name << "::#{singular_class_name}"
|
1344
1384
|
skip_controller = nil
|
1345
|
-
|
1346
|
-
|
1347
|
-
(
|
1348
|
-
|
1349
|
-
|
1350
|
-
|
1351
|
-
|
1385
|
+
begin
|
1386
|
+
if plural_class_name == 'BrickOpenapi' ||
|
1387
|
+
(
|
1388
|
+
(::Brick.config.add_status || ::Brick.config.add_orphans) &&
|
1389
|
+
plural_class_name == 'BrickGem'
|
1390
|
+
# Was: ) || (model = self.const_get(full_class_name))
|
1391
|
+
) || (model = Object.const_get(full_class_name))
|
1392
|
+
# puts "#{self.name} - #{full_class_name}"
|
1352
1393
|
|
1353
1394
|
# In the very rare case that we've picked up a MODULE which has the same name as what would be the
|
1354
1395
|
# resource's MODEL name, just build out an appropriate auto-model on-the-fly. (RailsDevs code has this in PayCustomer.)
|
@@ -1356,10 +1397,10 @@ end
|
|
1356
1397
|
if model && !model.is_a?(Class)
|
1357
1398
|
model, _code = Object.send(:build_model, relations, model.module_parent, model.module_parent.name, singular_class_name)
|
1358
1399
|
end
|
1359
|
-
rescue NameError # If the const_get for the model has failed...
|
1360
|
-
skip_controller = true
|
1361
|
-
# ... then just fall through and allow it to fail when trying to load the ____Controller class normally.
|
1362
1400
|
end
|
1401
|
+
rescue NameError # If the const_get for the model has failed...
|
1402
|
+
skip_controller = true
|
1403
|
+
# ... then just fall through and allow it to fail when trying to load the ____Controller class normally.
|
1363
1404
|
end
|
1364
1405
|
unless skip_controller
|
1365
1406
|
Object.send(:build_controller, self, class_name, plural_class_name, model, relations)
|
@@ -1452,7 +1493,7 @@ end
|
|
1452
1493
|
base_module._brick_const_missing(*args)
|
1453
1494
|
# elsif base_module != Object
|
1454
1495
|
# module_parent.const_missing(*args)
|
1455
|
-
elsif Rails.respond_to?(:autoloaders) && # After finding nothing else, if Zeitwerk is enabled ...
|
1496
|
+
elsif Object.const_defined?('Rails') && ::Rails.respond_to?(:autoloaders) && # After finding nothing else, if Zeitwerk is enabled ...
|
1456
1497
|
(Rails::Autoloaders.respond_to?(:zeitwerk_enabled?) ? Rails::Autoloaders.zeitwerk_enabled? : true)
|
1457
1498
|
self._brick_const_missing(*args) # ... rely solely on Zeitwerk.
|
1458
1499
|
else # Classic mode
|
@@ -1477,7 +1518,9 @@ class Object
|
|
1477
1518
|
private
|
1478
1519
|
|
1479
1520
|
def build_model(relations, base_module, base_name, class_name, inheritable_name = nil)
|
1480
|
-
tnp = ::Brick.config.table_name_prefixes&.find { |p| p.last == base_module.name }
|
1521
|
+
tnp = ::Brick.config.table_name_prefixes&.find { |p| p.last == base_module.name }
|
1522
|
+
# return [base_module, ''] if !base_module.is_a?(Class) && base_name == tnp&.last
|
1523
|
+
|
1481
1524
|
if (base_model = (::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::#{class_name}", nil) || # Are we part of an auto-STI namespace? ...
|
1482
1525
|
::Brick.config.sti_namespace_prefixes&.fetch("::#{base_module.name}::", nil))&.constantize) ||
|
1483
1526
|
base_module != Object # ... or otherwise already in some namespace?
|
@@ -1499,7 +1542,7 @@ class Object
|
|
1499
1542
|
table_name = if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ::Brick.existing_stis[model_name]&.constantize)
|
1500
1543
|
base_model.table_name
|
1501
1544
|
else
|
1502
|
-
"#{tnp}#{ActiveSupport::Inflector.pluralize(singular_table_name)}"
|
1545
|
+
"#{tnp&.first}#{ActiveSupport::Inflector.pluralize(singular_table_name)}"
|
1503
1546
|
end
|
1504
1547
|
if ::Brick.apartment_multitenant &&
|
1505
1548
|
Apartment.excluded_models.include?(table_name.singularize.camelize)
|
@@ -1553,7 +1596,7 @@ class Object
|
|
1553
1596
|
else
|
1554
1597
|
# Class for auto-generated models to inherit from
|
1555
1598
|
base_model = (::Brick.config.models_inherit_from ||= (begin
|
1556
|
-
::ApplicationRecord
|
1599
|
+
Object.const_defined?('ApplicationRecord') ? ::ApplicationRecord : ::ActiveRecord::Base
|
1557
1600
|
rescue StandardError => ex
|
1558
1601
|
::ActiveRecord::Base
|
1559
1602
|
end))
|
@@ -1728,7 +1771,8 @@ class Object
|
|
1728
1771
|
end
|
1729
1772
|
|
1730
1773
|
def build_bt_or_hm(full_name, relations, relation, hmts, assoc, inverse_assoc_name, inverse_table, code)
|
1731
|
-
singular_table_name = inverse_table&.singularize
|
1774
|
+
return unless (singular_table_name = inverse_table&.singularize)
|
1775
|
+
|
1732
1776
|
options = {}
|
1733
1777
|
macro = if assoc[:is_bt]
|
1734
1778
|
# Try to take care of screwy names if this is a belongs_to going to an STI subclass
|
@@ -2592,586 +2636,46 @@ end
|
|
2592
2636
|
# ==========================================================
|
2593
2637
|
# Get info on all relations during first database connection
|
2594
2638
|
# ==========================================================
|
2639
|
+
if Object.const_defined?('OTR') && OTR.const_defined?('ActiveRecord')
|
2640
|
+
OTR::ActiveRecord.class_exec do
|
2641
|
+
class << self
|
2642
|
+
alias _brick_establish_connection! establish_connection!
|
2643
|
+
def establish_connection!(*args)
|
2644
|
+
conn = _brick_establish_connection!(*args)
|
2645
|
+
return conn unless ::Brick.config.mode == :on
|
2595
2646
|
|
2596
|
-
|
2597
|
-
|
2598
|
-
|
2599
|
-
|
2600
|
-
|
2601
|
-
alias _brick_establish_connection establish_connection
|
2602
|
-
def establish_connection(*args)
|
2603
|
-
conn = _brick_establish_connection(*args)
|
2604
|
-
return conn unless ::Brick.config.mode == :on
|
2605
|
-
|
2606
|
-
begin
|
2607
|
-
# Overwrite SQLite's #begin_db_transaction so it opens in IMMEDIATE mode instead of
|
2608
|
-
# the default DEFERRED mode.
|
2609
|
-
# https://discuss.rubyonrails.org/t/failed-write-transaction-upgrades-in-sqlite3/81480/2
|
2610
|
-
if ActiveRecord::Base.connection.adapter_name == 'SQLite'
|
2611
|
-
arca = ::ActiveRecord::ConnectionAdapters
|
2612
|
-
db_statements = arca::SQLite3.const_defined?('DatabaseStatements') ? arca::SQLite3::DatabaseStatements : arca::SQLite3::SchemaStatements
|
2613
|
-
# Rails 7.1 and later
|
2614
|
-
if arca::AbstractAdapter.private_instance_methods.include?(:with_raw_connection)
|
2615
|
-
db_statements.define_method(:begin_db_transaction) do
|
2616
|
-
log("begin immediate transaction", "TRANSACTION") do
|
2617
|
-
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
|
2618
|
-
conn.transaction(:immediate)
|
2619
|
-
end
|
2620
|
-
end
|
2621
|
-
end
|
2622
|
-
else # Rails < 7.1
|
2623
|
-
db_statements.define_method(:begin_db_transaction) do
|
2624
|
-
log('begin immediate transaction', 'TRANSACTION') { @connection.transaction(:immediate) }
|
2625
|
-
end
|
2647
|
+
begin
|
2648
|
+
# ::Brick.is_db_present = true
|
2649
|
+
::Brick.reflect_tables
|
2650
|
+
rescue ActiveRecord::NoDatabaseError
|
2651
|
+
# ::Brick.is_db_present = false
|
2626
2652
|
end
|
2653
|
+
Module.class_exec &::Brick::ADD_CONST_MISSING
|
2654
|
+
conn
|
2627
2655
|
end
|
2628
|
-
# ::Brick.is_db_present = true
|
2629
|
-
_brick_reflect_tables
|
2630
|
-
rescue ActiveRecord::NoDatabaseError
|
2631
|
-
# ::Brick.is_db_present = false
|
2632
2656
|
end
|
2633
|
-
conn
|
2634
2657
|
end
|
2658
|
+
else
|
2659
|
+
if ActiveRecord.const_defined?('ConnectionHandling')
|
2660
|
+
ActiveRecord::ConnectionHandling
|
2661
|
+
else
|
2662
|
+
ActiveRecord::ConnectionAdapters::ConnectionHandler
|
2663
|
+
end.class_exec do
|
2664
|
+
alias _brick_establish_connection establish_connection
|
2665
|
+
def establish_connection(*args)
|
2666
|
+
conn = _brick_establish_connection(*args)
|
2667
|
+
return conn unless ::Brick.config.mode == :on
|
2635
2668
|
|
2636
|
-
|
2637
|
-
|
2638
|
-
|
2639
|
-
|
2640
|
-
|
2641
|
-
# return if ActiveRecord::Base.connection.current_database == 'postgres'
|
2642
|
-
|
2643
|
-
orig_schema = nil
|
2644
|
-
if (relations = ::Brick.relations).keys == [:db_name]
|
2645
|
-
::Brick.remove_instance_variable(:@_additional_references_loaded) if ::Brick.instance_variable_defined?(:@_additional_references_loaded)
|
2646
|
-
|
2647
|
-
# --------------------------------------------
|
2648
|
-
# 1. Load three initializers early
|
2649
|
-
# (inflectsions.rb, brick.rb, apartment.rb)
|
2650
|
-
# Very first thing, load inflections since we'll be using .pluralize and .singularize on table and model names
|
2651
|
-
if File.exist?(inflections = ::Rails.root&.join('config/initializers/inflections.rb') || '')
|
2652
|
-
load inflections
|
2653
|
-
end
|
2654
|
-
# Now the Brick initializer since there may be important schema things configured
|
2655
|
-
if !::Brick.initializer_loaded && File.exist?(brick_initializer = ::Rails.root&.join('config/initializers/brick.rb') || '')
|
2656
|
-
::Brick.initializer_loaded = load brick_initializer
|
2657
|
-
|
2658
|
-
# After loading the initializer, add compatibility for ActiveStorage and ActionText if those haven't already been
|
2659
|
-
# defined. (Further JSON configuration for ActiveStorage metadata happens later in the after_initialize hook.)
|
2660
|
-
['ActiveStorage', 'ActionText'].each do |ar_extension|
|
2661
|
-
if Object.const_defined?(ar_extension) &&
|
2662
|
-
(extension = Object.const_get(ar_extension)).respond_to?(:table_name_prefix) &&
|
2663
|
-
!::Brick.config.table_name_prefixes.key?(as_tnp = extension.table_name_prefix)
|
2664
|
-
::Brick.config.table_name_prefixes[as_tnp] = ar_extension
|
2665
|
-
end
|
2666
|
-
end
|
2667
|
-
|
2668
|
-
# Support the followability gem: https://github.com/nejdetkadir/followability
|
2669
|
-
if Object.const_defined?('Followability') && !::Brick.config.table_name_prefixes.key?('followability_')
|
2670
|
-
::Brick.config.table_name_prefixes['followability_'] = 'Followability'
|
2671
|
-
end
|
2672
|
-
end
|
2673
|
-
# Load the initializer for the Apartment gem a little early so that if .excluded_models and
|
2674
|
-
# .default_schema are specified then we can work with non-tenanted models more appropriately
|
2675
|
-
if (apartment = Object.const_defined?('Apartment')) &&
|
2676
|
-
File.exist?(apartment_initializer = ::Rails.root.join('config/initializers/apartment.rb'))
|
2677
|
-
require 'apartment/adapters/abstract_adapter'
|
2678
|
-
Apartment::Adapters::AbstractAdapter.class_exec do
|
2679
|
-
if instance_methods.include?(:process_excluded_models)
|
2680
|
-
def process_excluded_models
|
2681
|
-
# All other models will share a connection (at Apartment.connection_class) and we can modify at will
|
2682
|
-
Apartment.excluded_models.each do |excluded_model|
|
2683
|
-
begin
|
2684
|
-
process_excluded_model(excluded_model)
|
2685
|
-
rescue NameError => e
|
2686
|
-
(@bad_models ||= []) << excluded_model
|
2687
|
-
end
|
2688
|
-
end
|
2689
|
-
end
|
2690
|
-
end
|
2691
|
-
end
|
2692
|
-
unless @_apartment_loaded
|
2693
|
-
load apartment_initializer
|
2694
|
-
@_apartment_loaded = true
|
2695
|
-
end
|
2696
|
-
end
|
2697
|
-
# Only for Postgres (Doesn't work in sqlite3 or MySQL)
|
2698
|
-
# puts ActiveRecord::Base.execute_sql("SELECT current_setting('SEARCH_PATH')").to_a.inspect
|
2699
|
-
|
2700
|
-
# ---------------------------
|
2701
|
-
# 2. Figure out schema things
|
2702
|
-
is_postgres = nil
|
2703
|
-
is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer'
|
2704
|
-
case ActiveRecord::Base.connection.adapter_name
|
2705
|
-
when 'PostgreSQL', 'SQLServer'
|
2706
|
-
is_postgres = !is_mssql
|
2707
|
-
db_schemas = if is_postgres
|
2708
|
-
ActiveRecord::Base.execute_sql('SELECT nspname AS table_schema, MAX(oid) AS dt FROM pg_namespace GROUP BY 1 ORDER BY 1;')
|
2709
|
-
else
|
2710
|
-
ActiveRecord::Base.execute_sql('SELECT DISTINCT table_schema, NULL AS dt FROM INFORMATION_SCHEMA.tables;')
|
2711
|
-
end
|
2712
|
-
::Brick.db_schemas = db_schemas.each_with_object({}) do |row, s|
|
2713
|
-
row = case row
|
2714
|
-
when Array
|
2715
|
-
row
|
2716
|
-
else
|
2717
|
-
[row['table_schema'], row['dt']]
|
2718
|
-
end
|
2719
|
-
# Remove any system schemas
|
2720
|
-
s[row.first] = { dt: row.last } unless ['information_schema', 'pg_catalog', 'pg_toast', 'heroku_ext',
|
2721
|
-
'INFORMATION_SCHEMA', 'sys'].include?(row.first)
|
2722
|
-
end
|
2723
|
-
possible_schema, possible_schemas, multitenancy = ::Brick.get_possible_schemas
|
2724
|
-
if possible_schemas
|
2725
|
-
if possible_schema
|
2726
|
-
::Brick.default_schema = ::Brick.apartment_default_tenant
|
2727
|
-
schema = possible_schema
|
2728
|
-
orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
2729
|
-
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
2730
|
-
# When testing, just find the most recently-created schema
|
2731
|
-
elsif begin
|
2732
|
-
Rails.env == 'test' ||
|
2733
|
-
ActiveRecord::Base.execute_sql("SELECT value FROM ar_internal_metadata WHERE key='environment';").first&.fetch('value', nil) == 'test'
|
2734
|
-
rescue
|
2735
|
-
end
|
2736
|
-
::Brick.default_schema = ::Brick.apartment_default_tenant
|
2737
|
-
::Brick.test_schema = schema = ::Brick.db_schemas.to_a.sort { |a, b| b.last[:dt] <=> a.last[:dt] }.first.first
|
2738
|
-
if possible_schema.blank?
|
2739
|
-
puts "While running tests, using the most recently-created schema, #{schema}."
|
2740
|
-
else
|
2741
|
-
puts "While running tests, had noticed in the brick.rb initializer that the line \"::Brick.schema_behavior = ...\" refers to a schema called \"#{possible_schema}\" which does not exist. Reading table structure from the most recently-created schema, #{schema}."
|
2742
|
-
end
|
2743
|
-
orig_schema = ActiveRecord::Base.execute_sql('SELECT current_schemas(true)').first['current_schemas'][1..-2].split(',')
|
2744
|
-
::Brick.config.schema_behavior = { multitenant: {} } # schema_to_analyse: [schema]
|
2745
|
-
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", schema)
|
2746
|
-
else
|
2747
|
-
puts "*** In the brick.rb initializer the line \"::Brick.schema_behavior = ...\" refers to schema(s) called #{possible_schemas.map { |s| "\"#{s}\"" }.join(', ')}. No mentioned schema exists. ***"
|
2748
|
-
if ::Brick.db_schemas.key?(::Brick.apartment_default_tenant)
|
2749
|
-
::Brick.default_schema = schema = ::Brick.apartment_default_tenant
|
2750
|
-
end
|
2751
|
-
end
|
2752
|
-
end
|
2753
|
-
when 'Mysql2', 'Trilogy'
|
2754
|
-
::Brick.default_schema = schema = ActiveRecord::Base.connection.current_database
|
2755
|
-
when 'OracleEnhanced'
|
2756
|
-
# ActiveRecord::Base.connection.current_database will be something like "XEPDB1"
|
2757
|
-
::Brick.default_schema = schema = ActiveRecord::Base.connection.raw_connection.username
|
2758
|
-
::Brick.db_schemas = {}
|
2759
|
-
ActiveRecord::Base.execute_sql("SELECT username FROM sys.all_users WHERE ORACLE_MAINTAINED != 'Y'").each { |s| ::Brick.db_schemas[s.first] = {} }
|
2760
|
-
when 'SQLite'
|
2761
|
-
sql = "SELECT m.name AS relation_name, UPPER(m.type) AS table_type,
|
2762
|
-
p.name AS column_name, p.type AS data_type,
|
2763
|
-
CASE p.pk WHEN 1 THEN 'PRIMARY KEY' END AS const
|
2764
|
-
FROM sqlite_master AS m
|
2765
|
-
INNER JOIN pragma_table_info(m.name) AS p
|
2766
|
-
WHERE m.name NOT IN ('sqlite_sequence', ?, ?)
|
2767
|
-
ORDER BY m.name, p.cid"
|
2768
|
-
else
|
2769
|
-
puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
|
2770
|
-
end
|
2771
|
-
|
2772
|
-
::Brick.db_schemas ||= {}
|
2773
|
-
|
2774
|
-
# ---------------------
|
2775
|
-
# 3. Tables and columns
|
2776
|
-
# %%% Retrieve internal ActiveRecord table names like this:
|
2777
|
-
# ActiveRecord::Base.internal_metadata_table_name, ActiveRecord::Base.schema_migrations_table_name
|
2778
|
-
# For if it's not SQLite -- so this is the Postgres and MySQL version
|
2779
|
-
measures = []
|
2780
|
-
::Brick.is_oracle = true if ActiveRecord::Base.connection.adapter_name == 'OracleEnhanced'
|
2781
|
-
case ActiveRecord::Base.connection.adapter_name
|
2782
|
-
when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
|
2783
|
-
# schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
2784
|
-
retrieve_schema_and_tables(sql, is_postgres, is_mssql, schema).each do |r|
|
2785
|
-
# If Apartment gem lists the table as being associated with a non-tenanted model then use whatever it thinks
|
2786
|
-
# is the default schema, usually 'public'.
|
2787
|
-
schema_name = if ::Brick.config.schema_behavior[:multitenant]
|
2788
|
-
::Brick.apartment_default_tenant if ::Brick.is_apartment_excluded_table(r['relation_name'])
|
2789
|
-
elsif ![schema, 'public'].include?(r['schema'])
|
2790
|
-
r['schema']
|
2791
|
-
end
|
2792
|
-
relation_name = schema_name ? "#{schema_name}.#{r['relation_name']}" : r['relation_name']
|
2793
|
-
# Both uppers and lowers as well as underscores?
|
2794
|
-
::Brick.apply_double_underscore_patch if relation_name =~ /[A-Z]/ && relation_name =~ /[a-z]/ && relation_name.index('_')
|
2795
|
-
relation = relations[relation_name]
|
2796
|
-
relation[:isView] = true if r['table_type'] == 'VIEW'
|
2797
|
-
relation[:description] = r['table_description'] if r['table_description']
|
2798
|
-
col_name = r['column_name']
|
2799
|
-
key = case r['const']
|
2800
|
-
when 'PRIMARY KEY'
|
2801
|
-
relation[:pkey][r['key'] || relation_name] ||= []
|
2802
|
-
when 'UNIQUE'
|
2803
|
-
relation[:ukeys][r['key'] || "#{relation_name}.#{col_name}"] ||= []
|
2804
|
-
# key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
|
2805
|
-
# key[r['key']]
|
2806
|
-
else
|
2807
|
-
if r['data_type'] == 'uuid'
|
2808
|
-
# && r['column_name'] == ::Brick.ar_base.primary_key
|
2809
|
-
# binding.pry
|
2810
|
-
relation[:pkey][r['key'] || relation_name] ||= []
|
2811
|
-
end
|
2812
|
-
end
|
2813
|
-
# binding.pry if key && r['data_type'] == 'uuid'
|
2814
|
-
key << col_name if key
|
2815
|
-
cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
|
2816
|
-
cols[col_name] = [r['data_type'], r['max_length'], measures&.include?(col_name), r['is_nullable'] == 'NO']
|
2817
|
-
# puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
|
2818
|
-
relation[:col_descrips][col_name] = r['column_description'] if r['column_description']
|
2819
|
-
end
|
2820
|
-
else # MySQL2, OracleEnhanced, and MSSQL act a little differently, bringing back an array for each row
|
2821
|
-
schema_and_tables = case ActiveRecord::Base.connection.adapter_name
|
2822
|
-
when 'OracleEnhanced'
|
2823
|
-
sql =
|
2824
|
-
"SELECT c.owner AS schema, c.table_name AS relation_name,
|
2825
|
-
CASE WHEN v.owner IS NULL THEN 'BASE_TABLE' ELSE 'VIEW' END AS table_type,
|
2826
|
-
c.column_name, c.data_type,
|
2827
|
-
COALESCE(c.data_length, c.data_precision) AS max_length,
|
2828
|
-
CASE ac.constraint_type WHEN 'P' THEN 'PRIMARY KEY' END AS const,
|
2829
|
-
ac.constraint_name AS \"key\",
|
2830
|
-
CASE c.nullable WHEN 'Y' THEN 'YES' ELSE 'NO' END AS is_nullable
|
2831
|
-
FROM all_tab_cols c
|
2832
|
-
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
|
2833
|
-
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'
|
2834
|
-
LEFT OUTER JOIN all_views v ON c.owner = v.owner AND c.table_name = v.view_name
|
2835
|
-
WHERE c.owner IN (#{::Brick.db_schemas.keys.map { |s| "'#{s}'" }.join(', ')})
|
2836
|
-
AND c.table_name NOT IN (?, ?)
|
2837
|
-
ORDER BY 1, 2, c.internal_column_id, acc.position"
|
2838
|
-
ActiveRecord::Base.execute_sql(sql, *ar_tables)
|
2839
|
-
else
|
2840
|
-
retrieve_schema_and_tables(sql)
|
2841
|
-
end
|
2842
|
-
|
2843
|
-
schema_and_tables.each do |r|
|
2844
|
-
next if r[1].index('$') # Oracle can have goofy table names with $
|
2845
|
-
|
2846
|
-
if (relation_name = r[1]) =~ /^[A-Z0-9_]+$/
|
2847
|
-
relation_name.downcase!
|
2848
|
-
# Both uppers and lowers as well as underscores?
|
2849
|
-
elsif relation_name =~ /[A-Z]/ && relation_name =~ /[a-z]/ && relation_name.index('_')
|
2850
|
-
::Brick.apply_double_underscore_patch
|
2851
|
-
end
|
2852
|
-
# Expect the default schema for SQL Server to be 'dbo'.
|
2853
|
-
if (::Brick.is_oracle && r[0] != schema) || (is_mssql && r[0] != 'dbo')
|
2854
|
-
relation_name = "#{r[0]}.#{relation_name}"
|
2855
|
-
end
|
2856
|
-
|
2857
|
-
relation = relations[relation_name] # here relation represents a table or view from the database
|
2858
|
-
relation[:isView] = true if r[2] == 'VIEW' # table_type
|
2859
|
-
col_name = ::Brick.is_oracle ? connection.send(:oracle_downcase, r[3]) : r[3]
|
2860
|
-
key = case r[6] # constraint type
|
2861
|
-
when 'PRIMARY KEY'
|
2862
|
-
# key
|
2863
|
-
relation[:pkey][r[7] || relation_name] ||= []
|
2864
|
-
when 'UNIQUE'
|
2865
|
-
relation[:ukeys][r[7] || "#{relation_name}.#{col_name}"] ||= []
|
2866
|
-
# key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
|
2867
|
-
# key[r['key']]
|
2868
|
-
end
|
2869
|
-
key << col_name if key
|
2870
|
-
cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
|
2871
|
-
# 'data_type', 'max_length', measure, 'is_nullable'
|
2872
|
-
cols[col_name] = [r[4], r[5], measures&.include?(col_name), r[8] == 'NO']
|
2873
|
-
# puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
|
2874
|
-
end
|
2875
|
-
end
|
2876
|
-
|
2877
|
-
# PostGIS adds three views which would confuse Rails if models were to be built for them.
|
2878
|
-
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
2879
|
-
if relations.key?('geography_columns') && relations.key?('geometry_columns') && relations.key?('spatial_ref_sys')
|
2880
|
-
(::Brick.config.exclude_tables ||= []) << 'geography_columns'
|
2881
|
-
::Brick.config.exclude_tables << 'geometry_columns'
|
2882
|
-
::Brick.config.exclude_tables << 'spatial_ref_sys'
|
2883
|
-
end
|
2884
|
-
end
|
2885
|
-
|
2886
|
-
# # Add unique OIDs
|
2887
|
-
# if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
2888
|
-
# ActiveRecord::Base.execute_sql(
|
2889
|
-
# "SELECT c.oid, n.nspname, c.relname
|
2890
|
-
# FROM pg_catalog.pg_namespace AS n
|
2891
|
-
# INNER JOIN pg_catalog.pg_class AS c ON n.oid = c.relnamespace
|
2892
|
-
# WHERE c.relkind IN ('r', 'v')"
|
2893
|
-
# ).each do |r|
|
2894
|
-
# next if ['pg_catalog', 'information_schema', ''].include?(r['nspname']) ||
|
2895
|
-
# ['ar_internal_metadata', 'schema_migrations'].include?(r['relname'])
|
2896
|
-
# relation = relations.fetch(r['relname'], nil)
|
2897
|
-
# if relation
|
2898
|
-
# (relation[:oid] ||= {})[r['nspname']] = r['oid']
|
2899
|
-
# else
|
2900
|
-
# puts "Where is #{r['nspname']} #{r['relname']} ?"
|
2901
|
-
# end
|
2902
|
-
# end
|
2903
|
-
# end
|
2904
|
-
# schema = ::Brick.default_schema # Reset back for this next round of fun
|
2905
|
-
|
2906
|
-
# ---------------------------------------------
|
2907
|
-
# 4. Foreign key info
|
2908
|
-
# (done in two parts which get JOINed together in Ruby code)
|
2909
|
-
kcus = nil
|
2910
|
-
entry_type = nil
|
2911
|
-
case ActiveRecord::Base.connection.adapter_name
|
2912
|
-
when 'PostgreSQL', 'Mysql2', 'Trilogy', 'SQLServer'
|
2913
|
-
# Part 1 -- all KCUs
|
2914
|
-
sql = "SELECT CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME, ORDINAL_POSITION,
|
2915
|
-
TABLE_NAME, COLUMN_NAME
|
2916
|
-
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE#{"
|
2917
|
-
WHERE CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema }#{"
|
2918
|
-
WHERE CONSTRAINT_SCHEMA = '#{ActiveRecord::Base.connection.current_database&.tr("'", "''")}'" if is_mysql
|
2919
|
-
}"
|
2920
|
-
kcus = ActiveRecord::Base.execute_sql(sql).each_with_object({}) do |v, s|
|
2921
|
-
if (entry_type ||= v.is_a?(Array) ? :array : :hash) == :hash
|
2922
|
-
key = "#{v['constraint_name']}.#{v['constraint_schema']}.#{v['constraint_catalog']}.#{v['ordinal_position']}"
|
2923
|
-
key << ".#{v['table_name']}.#{v['column_name']}" unless is_postgres || is_mssql
|
2924
|
-
s[key] = [v['constraint_schema'], v['table_name']]
|
2925
|
-
else # Array
|
2926
|
-
key = "#{v[2]}.#{v[1]}.#{v[0]}.#{v[3]}"
|
2927
|
-
key << ".#{v[4]}.#{v[5]}" unless is_postgres || is_mssql
|
2928
|
-
s[key] = [v[1], v[4]]
|
2929
|
-
end
|
2930
|
-
end
|
2931
|
-
|
2932
|
-
# Part 2 -- fk_references
|
2933
|
-
sql = "SELECT kcu.CONSTRAINT_SCHEMA, kcu.TABLE_NAME, kcu.COLUMN_NAME,
|
2934
|
-
#{# These will get filled in with real values (effectively doing the JOIN in Ruby)
|
2935
|
-
is_postgres || is_mssql ? 'NULL as primary_schema, NULL as primary_table' :
|
2936
|
-
'kcu.REFERENCED_TABLE_NAME, kcu.REFERENCED_COLUMN_NAME'},
|
2937
|
-
kcu.CONSTRAINT_NAME AS CONSTRAINT_SCHEMA_FK,
|
2938
|
-
rc.UNIQUE_CONSTRAINT_NAME, rc.UNIQUE_CONSTRAINT_SCHEMA, rc.UNIQUE_CONSTRAINT_CATALOG, kcu.ORDINAL_POSITION
|
2939
|
-
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
|
2940
|
-
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu
|
2941
|
-
ON kcu.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
|
2942
|
-
AND kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
|
2943
|
-
AND kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME#{"
|
2944
|
-
WHERE kcu.CONSTRAINT_SCHEMA = COALESCE(current_setting('SEARCH_PATH'), 'public')" if is_postgres && schema}#{"
|
2945
|
-
WHERE kcu.CONSTRAINT_SCHEMA = '#{ActiveRecord::Base.connection.current_database&.tr("'", "''")}'" if is_mysql}"
|
2946
|
-
fk_references = ActiveRecord::Base.execute_sql(sql)
|
2947
|
-
when 'SQLite'
|
2948
|
-
sql = "SELECT NULL AS constraint_schema, m.name, fkl.\"from\", NULL AS primary_schema, fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
|
2949
|
-
FROM sqlite_master m
|
2950
|
-
INNER JOIN pragma_foreign_key_list(m.name) fkl ON m.type = 'table'
|
2951
|
-
ORDER BY m.name, fkl.seq"
|
2952
|
-
fk_references = ActiveRecord::Base.execute_sql(sql)
|
2953
|
-
when 'OracleEnhanced'
|
2954
|
-
schemas = ::Brick.db_schemas.keys.map { |s| "'#{s}'" }.join(', ')
|
2955
|
-
sql =
|
2956
|
-
"SELECT -- fk
|
2957
|
-
ac.owner AS constraint_schema, acc_fk.table_name, acc_fk.column_name,
|
2958
|
-
-- referenced pk
|
2959
|
-
ac.r_owner AS primary_schema, acc_pk.table_name AS primary_table, acc_fk.constraint_name AS constraint_schema_fk
|
2960
|
-
-- , acc_pk.column_name
|
2961
|
-
FROM all_cons_columns acc_fk
|
2962
|
-
INNER JOIN all_constraints ac ON acc_fk.owner = ac.owner
|
2963
|
-
AND acc_fk.constraint_name = ac.constraint_name
|
2964
|
-
INNER JOIN all_cons_columns acc_pk ON ac.r_owner = acc_pk.owner
|
2965
|
-
AND ac.r_constraint_name = acc_pk.constraint_name
|
2966
|
-
WHERE ac.constraint_type = 'R'
|
2967
|
-
AND ac.owner IN (#{schemas})
|
2968
|
-
AND ac.r_owner IN (#{schemas})"
|
2969
|
-
fk_references = ActiveRecord::Base.execute_sql(sql)
|
2970
|
-
end
|
2971
|
-
::Brick.is_oracle = true if ActiveRecord::Base.connection.adapter_name == 'OracleEnhanced'
|
2972
|
-
# ::Brick.default_schema ||= schema ||= 'public' if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
2973
|
-
::Brick.default_schema ||= 'public' if is_postgres
|
2974
|
-
fk_references&.each do |fk|
|
2975
|
-
fk = fk.values unless fk.is_a?(Array)
|
2976
|
-
# Virtually JOIN KCUs to fk_references in order to fill in the primary schema and primary table
|
2977
|
-
kcu_key = "#{fk[6]}.#{fk[7]}.#{fk[8]}.#{fk[9]}"
|
2978
|
-
kcu_key << ".#{fk[3]}.#{fk[4]}" unless is_postgres || is_mssql
|
2979
|
-
if (kcu = kcus&.fetch(kcu_key, nil))
|
2980
|
-
fk[3] = kcu[0]
|
2981
|
-
fk[4] = kcu[1]
|
2982
|
-
end
|
2983
|
-
# Multitenancy makes things a little more general overall, except for non-tenanted tables
|
2984
|
-
if ::Brick.is_apartment_excluded_table(::Brick.namify(fk[1]))
|
2985
|
-
fk[0] = ::Brick.apartment_default_tenant
|
2986
|
-
elsif (is_postgres && (fk[0] == 'public' || (multitenancy && fk[0] == schema))) ||
|
2987
|
-
(::Brick.is_oracle && fk[0] == schema) ||
|
2988
|
-
(is_mssql && fk[0] == 'dbo') ||
|
2989
|
-
(!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[0]))
|
2990
|
-
fk[0] = nil
|
2991
|
-
end
|
2992
|
-
if ::Brick.is_apartment_excluded_table(fk[4])
|
2993
|
-
fk[3] = ::Brick.apartment_default_tenant
|
2994
|
-
elsif (is_postgres && (fk[3] == 'public' || (multitenancy && fk[3] == schema))) ||
|
2995
|
-
(::Brick.is_oracle && fk[3] == schema) ||
|
2996
|
-
(is_mssql && fk[3] == 'dbo') ||
|
2997
|
-
(!is_postgres && !::Brick.is_oracle && !is_mssql && ['mysql', 'performance_schema', 'sys'].exclude?(fk[3]))
|
2998
|
-
fk[3] = nil
|
2999
|
-
end
|
3000
|
-
if ::Brick.is_oracle
|
3001
|
-
fk[1].downcase! if fk[1] =~ /^[A-Z0-9_]+$/
|
3002
|
-
fk[4].downcase! if fk[4] =~ /^[A-Z0-9_]+$/
|
3003
|
-
fk[2] = connection.send(:oracle_downcase, fk[2])
|
3004
|
-
end
|
3005
|
-
::Brick._add_bt_and_hm(fk, relations)
|
3006
|
-
end
|
3007
|
-
kcus = nil # Allow this large item to be garbage collected
|
3008
|
-
end
|
3009
|
-
|
3010
|
-
table_name_lookup = (::Brick.table_name_lookup ||= {})
|
3011
|
-
relations.each do |k, v|
|
3012
|
-
next if k.is_a?(Symbol)
|
3013
|
-
|
3014
|
-
rel_name = k.split('.').map { |rel_part| ::Brick.namify(rel_part, :underscore) }
|
3015
|
-
schema_names = rel_name[0..-2]
|
3016
|
-
schema_names.shift if ::Brick.apartment_multitenant && schema_names.first == ::Brick.apartment_default_tenant
|
3017
|
-
v[:schema] = schema_names.join('.') unless schema_names.empty?
|
3018
|
-
# %%% If more than one schema has the same table name, will need to add a schema name prefix to have uniqueness
|
3019
|
-
if (singular = rel_name.last.singularize).blank?
|
3020
|
-
singular = rel_name.last
|
3021
|
-
end
|
3022
|
-
name_parts = if (tnp = ::Brick.config.table_name_prefixes
|
3023
|
-
&.find { |k1, _v1| singular.start_with?(k1) && singular.length > k1.length }
|
3024
|
-
).present?
|
3025
|
-
v[:auto_prefixed_schema] = tnp.first
|
3026
|
-
# v[:resource] = rel_name.last[tnp.first.length..-1]
|
3027
|
-
[tnp.last, singular[tnp.first.length..-1]]
|
3028
|
-
else
|
3029
|
-
# v[:resource] = rel_name.last
|
3030
|
-
[singular]
|
3031
|
-
end
|
3032
|
-
proposed_name_parts = (schema_names + name_parts).map { |p| ::Brick.namify(p, :underscore).camelize }
|
3033
|
-
# Find out if the proposed name leads to a module or class that already exists and is not an AR class
|
3034
|
-
colliding_thing = nil
|
3035
|
-
loop do
|
3036
|
-
klass = Object
|
3037
|
-
proposed_name_parts.each do |part|
|
3038
|
-
if klass.const_defined?(part)
|
3039
|
-
klass = klass.const_get(part)
|
3040
|
-
else
|
3041
|
-
klass = nil
|
3042
|
-
break
|
3043
|
-
end
|
3044
|
-
end
|
3045
|
-
break if !klass || (klass < ActiveRecord::Base) # Break if all good -- no conflicts
|
3046
|
-
|
3047
|
-
# Find a unique name since there's already something that's non-AR with that same name
|
3048
|
-
last_idx = proposed_name_parts.length - 1
|
3049
|
-
proposed_name_parts[last_idx] = ::Brick.ensure_unique(proposed_name_parts[last_idx], 'X')
|
3050
|
-
colliding_thing ||= klass
|
3051
|
-
end
|
3052
|
-
v[:class_name] = proposed_name_parts.join('::')
|
3053
|
-
# Was: v[:resource] = v[:class_name].underscore.tr('/', '.')
|
3054
|
-
v[:resource] = proposed_name_parts.last.underscore
|
3055
|
-
if colliding_thing
|
3056
|
-
message_start = if colliding_thing.is_a?(Module) && Object.const_defined?(:Rails) &&
|
3057
|
-
colliding_thing.constants.find { |c| colliding_thing.const_get(c) < Rails::Application }
|
3058
|
-
"The module for the Rails application itself, \"#{colliding_thing.name}\","
|
3059
|
-
else
|
3060
|
-
"Non-AR #{colliding_thing.class.name.downcase} \"#{colliding_thing.name}\""
|
3061
|
-
end
|
3062
|
-
puts "WARNING: #{message_start} already exists.\n Will set up to auto-create model #{v[:class_name]} for table #{k}."
|
3063
|
-
end
|
3064
|
-
# Track anything that's out-of-the-ordinary
|
3065
|
-
table_name_lookup[v[:class_name]] = k unless v[:class_name].underscore.pluralize == k
|
3066
|
-
end
|
3067
|
-
::Brick.load_additional_references if ::Brick.initializer_loaded
|
3068
|
-
|
3069
|
-
if is_postgres
|
3070
|
-
params = []
|
3071
|
-
ActiveRecord::Base.execute_sql("-- inherited and partitioned tables counts
|
3072
|
-
SELECT n.nspname, parent.relname,
|
3073
|
-
((SUM(child.reltuples::float) / greatest(SUM(child.relpages), 1))) *
|
3074
|
-
(SUM(pg_relation_size(child.oid))::float / (current_setting('block_size')::float))::integer AS rowcount
|
3075
|
-
FROM pg_inherits
|
3076
|
-
INNER JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
|
3077
|
-
INNER JOIN pg_class child ON pg_inherits.inhrelid = child.oid
|
3078
|
-
INNER JOIN pg_catalog.pg_namespace n ON n.oid = parent.relnamespace#{
|
3079
|
-
if schema
|
3080
|
-
params << schema
|
3081
|
-
"
|
3082
|
-
WHERE n.nspname = COALESCE(?, 'public')"
|
3083
|
-
end}
|
3084
|
-
GROUP BY n.nspname, parent.relname, child.reltuples, child.relpages, child.oid
|
3085
|
-
|
3086
|
-
UNION ALL
|
3087
|
-
|
3088
|
-
-- table count
|
3089
|
-
SELECT n.nspname, pg_class.relname,
|
3090
|
-
(pg_class.reltuples::float / greatest(pg_class.relpages, 1)) *
|
3091
|
-
(pg_relation_size(pg_class.oid)::float / (current_setting('block_size')::float))::integer AS rowcount
|
3092
|
-
FROM pg_class
|
3093
|
-
INNER JOIN pg_catalog.pg_namespace n ON n.oid = pg_class.relnamespace#{
|
3094
|
-
if schema
|
3095
|
-
params << schema
|
3096
|
-
"
|
3097
|
-
WHERE n.nspname = COALESCE(?, 'public')"
|
3098
|
-
end}
|
3099
|
-
GROUP BY n.nspname, pg_class.relname, pg_class.reltuples, pg_class.relpages, pg_class.oid", params).each do |tblcount|
|
3100
|
-
# %%% What is the default schema here?
|
3101
|
-
prefix = "#{tblcount['nspname']}." unless tblcount['nspname'] == (schema || 'public')
|
3102
|
-
relations.fetch("#{prefix}#{tblcount['relname']}", nil)&.[]=(:rowcount, tblcount['rowcount'].to_i.round)
|
2669
|
+
begin
|
2670
|
+
# ::Brick.is_db_present = true
|
2671
|
+
::Brick.reflect_tables
|
2672
|
+
rescue ActiveRecord::NoDatabaseError
|
2673
|
+
# ::Brick.is_db_present = false
|
3103
2674
|
end
|
2675
|
+
conn
|
3104
2676
|
end
|
3105
|
-
|
3106
|
-
if orig_schema && (orig_schema = (orig_schema - ['pg_catalog', 'pg_toast', 'heroku_ext']).first)
|
3107
|
-
puts "Now switching back to \"#{orig_schema}\" schema."
|
3108
|
-
ActiveRecord::Base.execute_sql("SET SEARCH_PATH = ?", orig_schema)
|
3109
|
-
end
|
3110
|
-
end
|
3111
|
-
|
3112
|
-
def retrieve_schema_and_tables(sql = nil, is_postgres = nil, is_mssql = nil, schema = nil)
|
3113
|
-
is_mssql = ActiveRecord::Base.connection.adapter_name == 'SQLServer' if is_mssql.nil?
|
3114
|
-
params = ar_tables
|
3115
|
-
sql ||= "SELECT t.table_schema AS \"schema\", t.table_name AS relation_name, t.table_type,#{"
|
3116
|
-
pg_catalog.obj_description(
|
3117
|
-
('\"' || t.table_schema || '\".\"' || t.table_name || '\"')::regclass::oid, 'pg_class'
|
3118
|
-
) AS table_description,
|
3119
|
-
pg_catalog.col_description(
|
3120
|
-
('\"' || t.table_schema || '\".\"' || t.table_name || '\"')::regclass::oid, c.ordinal_position
|
3121
|
-
) AS column_description," if is_postgres}
|
3122
|
-
c.column_name, #{is_postgres ? "CASE c.data_type WHEN 'USER-DEFINED' THEN pg_t.typname ELSE c.data_type END AS data_type" : 'c.data_type'},
|
3123
|
-
COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
|
3124
|
-
kcu.constraint_type AS const, kcu.constraint_name AS \"key\",
|
3125
|
-
c.is_nullable
|
3126
|
-
FROM INFORMATION_SCHEMA.tables AS t
|
3127
|
-
LEFT OUTER JOIN INFORMATION_SCHEMA.columns AS c ON t.table_schema = c.table_schema
|
3128
|
-
AND t.table_name = c.table_name
|
3129
|
-
LEFT OUTER JOIN
|
3130
|
-
(SELECT kcu1.constraint_schema, kcu1.table_name, kcu1.column_name, kcu1.ordinal_position,
|
3131
|
-
tc.constraint_type, kcu1.constraint_name
|
3132
|
-
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
|
3133
|
-
INNER JOIN INFORMATION_SCHEMA.table_constraints AS tc
|
3134
|
-
ON kcu1.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
|
3135
|
-
AND kcu1.TABLE_NAME = tc.TABLE_NAME
|
3136
|
-
AND kcu1.CONSTRAINT_NAME = tc.constraint_name
|
3137
|
-
AND tc.constraint_type != 'FOREIGN KEY' -- For MSSQL
|
3138
|
-
) AS kcu ON
|
3139
|
-
-- kcu.CONSTRAINT_CATALOG = t.table_catalog AND
|
3140
|
-
kcu.CONSTRAINT_SCHEMA = c.table_schema
|
3141
|
-
AND kcu.TABLE_NAME = c.table_name
|
3142
|
-
AND kcu.column_name = c.column_name#{"
|
3143
|
-
-- AND kcu.position_in_unique_constraint IS NULL" unless is_mssql}#{"
|
3144
|
-
INNER JOIN pg_catalog.pg_namespace pg_n ON pg_n.nspname = t.table_schema
|
3145
|
-
INNER JOIN pg_catalog.pg_class pg_c ON pg_n.oid = pg_c.relnamespace AND pg_c.relname = c.table_name
|
3146
|
-
INNER JOIN pg_catalog.pg_attribute pg_a ON pg_c.oid = pg_a.attrelid AND pg_a.attname = c.column_name
|
3147
|
-
INNER JOIN pg_catalog.pg_type pg_t ON pg_t.oid = pg_a.atttypid" if is_postgres}
|
3148
|
-
WHERE t.table_schema #{is_postgres || is_mssql ?
|
3149
|
-
"NOT IN ('information_schema', 'pg_catalog', 'pg_toast', 'heroku_ext',
|
3150
|
-
'INFORMATION_SCHEMA', 'sys')"
|
3151
|
-
:
|
3152
|
-
"= '#{ActiveRecord::Base.connection.current_database&.tr("'", "''")}'"}#{
|
3153
|
-
if is_postgres && schema
|
3154
|
-
params = params.unshift(schema) # Used to use this SQL: current_setting('SEARCH_PATH')
|
3155
|
-
"
|
3156
|
-
AND t.table_schema = COALESCE(?, 'public')"
|
3157
|
-
end}
|
3158
|
-
-- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
|
3159
|
-
AND t.table_name NOT IN ('pg_stat_statements', ?, ?)
|
3160
|
-
ORDER BY 1, t.table_type DESC, 2, c.ordinal_position"
|
3161
|
-
ActiveRecord::Base.execute_sql(sql, *params)
|
3162
|
-
end
|
3163
|
-
|
3164
|
-
def ar_tables
|
3165
|
-
ar_smtn = if ActiveRecord::Base.respond_to?(:schema_migrations_table_name)
|
3166
|
-
ActiveRecord::Base.schema_migrations_table_name
|
3167
|
-
else
|
3168
|
-
'schema_migrations'
|
3169
|
-
end
|
3170
|
-
ar_imtn = ActiveRecord.version >= ::Gem::Version.new('5.0') ? ActiveRecord::Base.internal_metadata_table_name : 'ar_internal_metadata'
|
3171
|
-
[ar_smtn, ar_imtn]
|
3172
2677
|
end
|
3173
2678
|
end
|
3174
|
-
|
3175
2679
|
# ==========================================
|
3176
2680
|
|
3177
2681
|
# :nodoc:
|
@@ -3464,30 +2968,36 @@ module Brick
|
|
3464
2968
|
end
|
3465
2969
|
end
|
3466
2970
|
|
3467
|
-
def _brick_index(tbl_name, mode = nil, separator = nil, relation = nil)
|
2971
|
+
def _brick_index(tbl_name, mode = nil, separator = nil, relation = nil, not_path = nil)
|
3468
2972
|
separator ||= '_'
|
3469
2973
|
res_name = (tbl_name_parts = tbl_name.split('.'))[0..-2].first
|
3470
2974
|
res_name << '.' if res_name
|
3471
2975
|
(res_name ||= +'') << (relation ||= ::Brick.relations.fetch(tbl_name, nil))&.fetch(:resource, nil) || tbl_name_parts.last
|
3472
2976
|
|
3473
|
-
res_parts = ((mode == :singular) ?
|
2977
|
+
res_parts = ((mode == :singular) ? res_name.singularize : res_name).split('.')
|
3474
2978
|
res_parts.shift if ::Brick.apartment_multitenant && res_parts.length > 1 && res_parts.first == ::Brick.apartment_default_tenant
|
3475
|
-
|
3476
|
-
|
2979
|
+
index2 = []
|
2980
|
+
if ::Brick.config.path_prefix
|
2981
|
+
res_parts.unshift(::Brick.config.path_prefix)
|
2982
|
+
index2 << ::Brick.config.path_prefix
|
2983
|
+
end
|
2984
|
+
if (aps = relation&.fetch(:auto_prefixed_schema, nil)) # && res_parts.last.start_with?(aps)
|
3477
2985
|
aps = aps[0..-2] if aps[-1] == '_'
|
2986
|
+
last_part = res_parts.last # [aps.length..-1]
|
3478
2987
|
res_parts[-1] = aps
|
3479
2988
|
res_parts << last_part
|
3480
|
-
|
3481
|
-
path_prefix = []
|
3482
|
-
if ::Brick.config.path_prefix
|
3483
|
-
res_parts.unshift(::Brick.config.path_prefix)
|
3484
|
-
path_prefix << ::Brick.config.path_prefix
|
2989
|
+
index2 << aps
|
3485
2990
|
end
|
3486
2991
|
index = res_parts.map(&:underscore).join(separator)
|
3487
|
-
|
3488
|
-
|
3489
|
-
|
3490
|
-
|
2992
|
+
if separator == 'x'
|
2993
|
+
index = index.tr('_', 'x')
|
2994
|
+
else
|
2995
|
+
# Rails applies an _index suffix to that route when the resource name isn't something plural
|
2996
|
+
index << '_index' if mode != :singular && !not_path &&
|
2997
|
+
index == (
|
2998
|
+
index2 + [relation[:class_name][(relation&.fetch(:auto_prefixed_class, nil)&.length&.+ 2) || 0..-1]&.underscore&.tr('/', '_') || '_']
|
2999
|
+
).join(separator)
|
3000
|
+
end
|
3491
3001
|
index
|
3492
3002
|
end
|
3493
3003
|
|