barkest_core 1.5.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +254 -0
- data/MIT-LICENSE +20 -0
- data/README.md +364 -0
- data/Rakefile +37 -0
- data/app/assets/fonts/barkest_core/ArchivoNarrow-Bold.ttf +0 -0
- data/app/assets/fonts/barkest_core/ArchivoNarrow-BoldItalic.ttf +0 -0
- data/app/assets/fonts/barkest_core/ArchivoNarrow-Italic.ttf +0 -0
- data/app/assets/fonts/barkest_core/ArchivoNarrow-Regular.ttf +0 -0
- data/app/assets/images/barkest_core/.keep +0 -0
- data/app/assets/images/barkest_core/barcode-B.svg +181 -0
- data/app/assets/javascripts/barkest_core/.keep +0 -0
- data/app/assets/javascripts/barkest_core/application.js +22 -0
- data/app/assets/javascripts/barkest_core/bootstrap-datepicker.js +1800 -0
- data/app/assets/javascripts/barkest_core/field_init.js +7 -0
- data/app/assets/javascripts/barkest_core/jquery.doubleScroll.js +112 -0
- data/app/assets/javascripts/barkest_core/masked_edit.js +25 -0
- data/app/assets/javascripts/barkest_core/system_status.js.erb +201 -0
- data/app/assets/stylesheets/barkest_core/.keep +0 -0
- data/app/assets/stylesheets/barkest_core/application.css +17 -0
- data/app/assets/stylesheets/barkest_core/custom.css.scss +264 -0
- data/app/assets/stylesheets/barkest_core/datepicker3.css +790 -0
- data/app/controllers/.keep +0 -0
- data/app/controllers/access_groups_controller.rb +74 -0
- data/app/controllers/account_activations_controller.rb +29 -0
- data/app/controllers/application_controller.rb +5 -0
- data/app/controllers/barkest_core/application_controller_base.rb +113 -0
- data/app/controllers/barkest_core/engine_controller_base.rb +15 -0
- data/app/controllers/barkest_core/testsub_controller.rb +21 -0
- data/app/controllers/contact_controller.rb +32 -0
- data/app/controllers/log_view_controller.rb +31 -0
- data/app/controllers/password_resets_controller.rb +126 -0
- data/app/controllers/sessions_controller.rb +64 -0
- data/app/controllers/status_controller.rb +150 -0
- data/app/controllers/system_config_controller.rb +238 -0
- data/app/controllers/system_update_controller.rb +164 -0
- data/app/controllers/test_access_controller.rb +44 -0
- data/app/controllers/test_report_controller.rb +75 -0
- data/app/controllers/users_controller.rb +218 -0
- data/app/helpers/.keep +0 -0
- data/app/helpers/barkest_core/application_helper.rb +134 -0
- data/app/helpers/barkest_core/form_helper.rb +469 -0
- data/app/helpers/barkest_core/html_helper.rb +70 -0
- data/app/helpers/barkest_core/misc_helper.rb +68 -0
- data/app/helpers/barkest_core/pdf_helper.rb +180 -0
- data/app/helpers/barkest_core/recaptcha_helper.rb +115 -0
- data/app/helpers/barkest_core/sessions_helper.rb +94 -0
- data/app/helpers/barkest_core/status_helper.rb +118 -0
- data/app/helpers/barkest_core/users_helper.rb +32 -0
- data/app/mailers/.keep +0 -0
- data/app/mailers/application_mailer.rb +5 -0
- data/app/mailers/barkest_core/application_mailer_base.rb +30 -0
- data/app/mailers/barkest_core/contact_form.rb +20 -0
- data/app/mailers/barkest_core/user_mailer.rb +44 -0
- data/app/models/.keep +0 -0
- data/app/models/access_group.rb +121 -0
- data/app/models/access_group_group_member.rb +13 -0
- data/app/models/access_group_user_member.rb +11 -0
- data/app/models/barkest_core/auth_config.rb +95 -0
- data/app/models/barkest_core/authorize_failure.rb +7 -0
- data/app/models/barkest_core/contact_message.rb +37 -0
- data/app/models/barkest_core/database_config.rb +223 -0
- data/app/models/barkest_core/db_table.rb +21 -0
- data/app/models/barkest_core/email_config.rb +132 -0
- data/app/models/barkest_core/global_status.rb +267 -0
- data/app/models/barkest_core/log_entry.rb +101 -0
- data/app/models/barkest_core/log_view_options.rb +51 -0
- data/app/models/barkest_core/ms_sql_db_definition.rb +441 -0
- data/app/models/barkest_core/ms_sql_definition.rb +221 -0
- data/app/models/barkest_core/ms_sql_function.rb +423 -0
- data/app/models/barkest_core/not_logged_in.rb +7 -0
- data/app/models/barkest_core/pdf_table_builder.rb +407 -0
- data/app/models/barkest_core/self_update_config.rb +37 -0
- data/app/models/barkest_core/user_alert.rb +29 -0
- data/app/models/barkest_core/user_alert_generators.rb +58 -0
- data/app/models/barkest_core/user_manager.rb +404 -0
- data/app/models/barkest_core/work_path.rb +74 -0
- data/app/models/disable_user.rb +18 -0
- data/app/models/ldap_access_group.rb +15 -0
- data/app/models/system_config.rb +99 -0
- data/app/models/user.rb +405 -0
- data/app/models/user_login_history.rb +11 -0
- data/app/views/.keep +0 -0
- data/app/views/access_groups/_form.html.erb +19 -0
- data/app/views/access_groups/edit.html.erb +2 -0
- data/app/views/access_groups/index.html.erb +32 -0
- data/app/views/access_groups/new.html.erb +2 -0
- data/app/views/access_groups/show.html.erb +4 -0
- data/app/views/barkest_core/contact_form/contact.html.erb +16 -0
- data/app/views/barkest_core/contact_form/contact.text.erb +13 -0
- data/app/views/barkest_core/testsub/_links.html.erb +5 -0
- data/app/views/barkest_core/testsub/page1.html.erb +3 -0
- data/app/views/barkest_core/testsub/page2.html.erb +2 -0
- data/app/views/barkest_core/testsub/page3.html.erb +2 -0
- data/app/views/barkest_core/user_mailer/account_activation.html.erb +7 -0
- data/app/views/barkest_core/user_mailer/account_activation.text.erb +6 -0
- data/app/views/barkest_core/user_mailer/invalid_password_reset.html.erb +3 -0
- data/app/views/barkest_core/user_mailer/invalid_password_reset.text.erb +5 -0
- data/app/views/barkest_core/user_mailer/password_reset.html.erb +8 -0
- data/app/views/barkest_core/user_mailer/password_reset.text.erb +7 -0
- data/app/views/contact/index.html.erb +24 -0
- data/app/views/layouts/_footer_copyright.html.erb +1 -0
- data/app/views/layouts/_menu_admin.html.erb +5 -0
- data/app/views/layouts/_menu_anon.html.erb +0 -0
- data/app/views/layouts/_menu_auth.html.erb +3 -0
- data/app/views/layouts/_menu_footer.html.erb +1 -0
- data/app/views/layouts/_nav_logo.html.erb +1 -0
- data/app/views/layouts/application.html.erb +2 -0
- data/app/views/layouts/barkest_core/_application.html.erb +24 -0
- data/app/views/layouts/barkest_core/_footer.html.erb +18 -0
- data/app/views/layouts/barkest_core/_header.html.erb +38 -0
- data/app/views/layouts/barkest_core/_html_mailer.html.erb +11 -0
- data/app/views/layouts/barkest_core/_menu_account.html.erb +14 -0
- data/app/views/layouts/barkest_core/_menu_sample.html.erb +1 -0
- data/app/views/layouts/barkest_core/_messages.html.erb +4 -0
- data/app/views/layouts/barkest_core/_shim.html.erb +4 -0
- data/app/views/layouts/barkest_core/_subheader.html.erb +1 -0
- data/app/views/layouts/barkest_core/_text_mailer.text.erb +4 -0
- data/app/views/layouts/mailer.html.erb +1 -0
- data/app/views/layouts/mailer.text.erb +1 -0
- data/app/views/log_view/index.html.erb +100 -0
- data/app/views/password_resets/edit.html.erb +20 -0
- data/app/views/password_resets/new.html.erb +14 -0
- data/app/views/sessions/new.html.erb +27 -0
- data/app/views/shared/_error_messages.html.erb +29 -0
- data/app/views/shared/_generic_user_alert.html.erb +4 -0
- data/app/views/status/current.html.erb +34 -0
- data/app/views/status/test.html.erb +50 -0
- data/app/views/system_config/index.html.erb +25 -0
- data/app/views/system_config/show_auth.html.erb +28 -0
- data/app/views/system_config/show_database.html.erb +36 -0
- data/app/views/system_config/show_email.html.erb +21 -0
- data/app/views/system_config/show_self_update.html.erb +13 -0
- data/app/views/system_update/index.html.erb +31 -0
- data/app/views/system_update/new.html.erb +2 -0
- data/app/views/test_access/allow_anon.html.erb +2 -0
- data/app/views/test_access/require_admin.html.erb +2 -0
- data/app/views/test_access/require_group_x.html.erb +2 -0
- data/app/views/test_access/require_user.html.erb +2 -0
- data/app/views/test_report/index.csv.csvrb +23 -0
- data/app/views/test_report/index.html.erb +6 -0
- data/app/views/test_report/index.pdf.prawn +50 -0
- data/app/views/test_report/index.xlsx.axlsx +28 -0
- data/app/views/users/_user.html.erb +57 -0
- data/app/views/users/_user_details.html.erb +15 -0
- data/app/views/users/_user_details_for_list.html.erb +1 -0
- data/app/views/users/_user_form.html.erb +13 -0
- data/app/views/users/disable_confirm.html.erb +19 -0
- data/app/views/users/edit.html.erb +15 -0
- data/app/views/users/index.html.erb +9 -0
- data/app/views/users/new.html.erb +10 -0
- data/app/views/users/show.html.erb +46 -0
- data/bin/rails +12 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20160617172539_create_access_groups.rb +10 -0
- data/db/migrate/20160617172725_create_users.rb +26 -0
- data/db/migrate/20160617172833_create_user_login_histories.rb +12 -0
- data/db/migrate/20160622151720_create_access_group_user_members.rb +9 -0
- data/db/migrate/20160622151925_create_access_group_group_members.rb +9 -0
- data/db/migrate/20160701005706_create_ldap_access_groups.rb +11 -0
- data/db/migrate/20161108155029_create_system_configs.rb +11 -0
- data/db/seeds/barkest_core_01_create_users.rb +42 -0
- data/db/seeds.rb +53 -0
- data/lib/barkest_core/concerns/association_with_defaults.rb +55 -0
- data/lib/barkest_core/concerns/boolean_parser.rb +88 -0
- data/lib/barkest_core/concerns/date_parser.rb +181 -0
- data/lib/barkest_core/concerns/email_tester.rb +55 -0
- data/lib/barkest_core/concerns/encrypted_fields.rb +156 -0
- data/lib/barkest_core/concerns/named_model.rb +73 -0
- data/lib/barkest_core/concerns/number_parser.rb +145 -0
- data/lib/barkest_core/concerns/utc_conversion.rb +60 -0
- data/lib/barkest_core/engine.rb +105 -0
- data/lib/barkest_core/extensions/active_record_extensions.rb +120 -0
- data/lib/barkest_core/extensions/application_configuration_extensions.rb +38 -0
- data/lib/barkest_core/extensions/application_extensions.rb +50 -0
- data/lib/barkest_core/extensions/axlsx_extenstions.rb +157 -0
- data/lib/barkest_core/extensions/fixture_set_extensions.rb +107 -0
- data/lib/barkest_core/extensions/generator_extensions.rb +271 -0
- data/lib/barkest_core/extensions/main_app_extensions.rb +35 -0
- data/lib/barkest_core/extensions/prawn_document_extensions.rb +367 -0
- data/lib/barkest_core/extensions/prawn_table_extensions.rb +131 -0
- data/lib/barkest_core/extensions/router_extensions.rb +106 -0
- data/lib/barkest_core/extensions/simple_formatter_extensions.rb +66 -0
- data/lib/barkest_core/extensions/test_case_extensions.rb +348 -0
- data/lib/barkest_core/extensions/time_extensions.rb +164 -0
- data/lib/barkest_core/handlers/csv_handler.rb +30 -0
- data/lib/barkest_core/version.rb +3 -0
- data/lib/barkest_core.rb +324 -0
- data/lib/generators/barkest/install_generator.rb +102 -0
- data/lib/generators/barkest_core/actions/01_patch_application_controller.rb +55 -0
- data/lib/generators/barkest_core/actions/02_patch_application_mailer.rb +56 -0
- data/lib/generators/barkest_core/actions/03_patch_assets.rb +62 -0
- data/lib/generators/barkest_core/actions/04_patch_layouts.rb +36 -0
- data/lib/generators/barkest_core/actions/05_patch_routes.rb +93 -0
- data/lib/generators/barkest_core/actions/06_patch_seeds.rb +60 -0
- data/lib/generators/barkest_core/actions/07_copy_migrations.rb +51 -0
- data/lib/generators/barkest_core/actions/08_configure_database.rb +52 -0
- data/lib/generators/barkest_core/actions/09_configure_secrets.rb +29 -0
- data/lib/generators/barkest_core/actions/99_patch_gitignore.rb +57 -0
- data/lib/generators/barkest_core/install_generator.rb +17 -0
- data/test/barkest_core_test.rb +83 -0
- data/test/controllers/access_groups_controller_test.rb +53 -0
- data/test/controllers/contact_controller_test.rb +10 -0
- data/test/controllers/sessions_controller_test.rb +10 -0
- data/test/controllers/users_controller_test.rb +10 -0
- data/test/dummy/.gitignore +10 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/javascripts/application.js +14 -0
- data/test/dummy/app/assets/stylesheets/application.css +16 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/mailers/.keep +0 -0
- data/test/dummy/app/mailers/application_mailer.rb +3 -0
- data/test/dummy/app/models/.keep +0 -0
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/views/layouts/application.html.erb +1 -0
- data/test/dummy/app/views/layouts/mailer.html.erb +1 -0
- data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/test/dummy/app/views/system_config/show_fake.html.erb +3 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config/application.rb +27 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +47 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +44 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/db_updater_ext.rb +33 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/sys_config_ext.rb +12 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +60 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/schema.rb +95 -0
- data/test/dummy/db/seeds/barkest_core_01_create_users.rb +42 -0
- data/test/dummy/db/seeds.rb +51 -0
- data/test/dummy/lib/assets/.keep +0 -0
- data/test/dummy/log/.keep +0 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/sql/my_test_view.sql +3 -0
- data/test/fixtures/access_groups.yml +21 -0
- data/test/fixtures/users.yml +71 -0
- data/test/helpers/barkest_core/sessions_helper_test.rb +22 -0
- data/test/integration/access_group_mgmt_test.rb +33 -0
- data/test/integration/access_test.rb +24 -0
- data/test/integration/account_activations_access_test.rb +12 -0
- data/test/integration/contact_test.rb +98 -0
- data/test/integration/extra_partial_test.rb +41 -0
- data/test/integration/log_view_access_test.rb +12 -0
- data/test/integration/password_resets_test.rb +101 -0
- data/test/integration/reports_test.rb +53 -0
- data/test/integration/status_access_test.rb +27 -0
- data/test/integration/system_config_access_test.rb +24 -0
- data/test/integration/system_update_access_test.rb +19 -0
- data/test/integration/users_access_test.rb +34 -0
- data/test/integration/users_edit_test.rb +178 -0
- data/test/integration/users_index_test.rb +62 -0
- data/test/integration/users_login_test.rb +67 -0
- data/test/integration/users_signup_test.rb +54 -0
- data/test/mailers/.keep +0 -0
- data/test/mailers/barkest_core/contact_form_test.rb +28 -0
- data/test/mailers/barkest_core/user_mailer_test.rb +43 -0
- data/test/mailers/previews/barkest_core/contact_form_preview.rb +17 -0
- data/test/mailers/previews/barkest_core/user_mailer_preview.rb +26 -0
- data/test/models/access_group_group_member_test.rb +28 -0
- data/test/models/access_group_test.rb +114 -0
- data/test/models/access_group_user_member_test.rb +28 -0
- data/test/models/barkest_core/auth_config_test.rb +57 -0
- data/test/models/barkest_core/bool_parser_test.rb +28 -0
- data/test/models/barkest_core/contact_message_test.rb +61 -0
- data/test/models/barkest_core/database_config_test.rb +33 -0
- data/test/models/barkest_core/date_parser_test.rb +110 -0
- data/test/models/barkest_core/email_config_test.rb +57 -0
- data/test/models/barkest_core/global_status_test.rb +50 -0
- data/test/models/barkest_core/ms_sql_db_updater_test.rb +115 -0
- data/test/models/barkest_core/ms_sql_definition_test.rb +102 -0
- data/test/models/barkest_core/ms_sql_function_test.rb +131 -0
- data/test/models/barkest_core/number_parser_test.rb +29 -0
- data/test/models/barkest_core/self_update_config_test.rb +29 -0
- data/test/models/barkest_core/user_alert_test.rb +19 -0
- data/test/models/barkest_core/user_manager_test.rb +34 -0
- data/test/models/barkest_core/work_path_test.rb +26 -0
- data/test/models/disable_user_test.rb +27 -0
- data/test/models/generic_time_test.rb +66 -0
- data/test/models/ldap_access_group_test.rb +31 -0
- data/test/models/pdf_table_builder_test.rb +6 -0
- data/test/models/system_config_test.rb +78 -0
- data/test/models/user_login_history_test.rb +37 -0
- data/test/models/user_test.rb +130 -0
- data/test/test_helper.rb +63 -0
- metadata +798 -0
@@ -0,0 +1,221 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
|
3
|
+
module BarkestCore
|
4
|
+
|
5
|
+
##
|
6
|
+
# Contains a SQL definition to create a single table, view, function, or procedure.
|
7
|
+
#
|
8
|
+
# SQL source is not validated, however simple checks are made to ensure that only
|
9
|
+
# one DDL statement is present unless you are creating a procedure in which case this
|
10
|
+
# check is skipped.
|
11
|
+
#
|
12
|
+
# To reference another object in your definition, prefix @Z~ to the beginning of the
|
13
|
+
# object name. For instance 'SELECT * FROM @Z~my_table' could be expanded to
|
14
|
+
# 'SELECT * FROM zz_barkest_core_my_table'.
|
15
|
+
#
|
16
|
+
# Function return types are grabbed as well so you know if your function is returning
|
17
|
+
# a table or an integral type.
|
18
|
+
class MsSqlDefinition
|
19
|
+
InvalidDefinition = Class.new(StandardError)
|
20
|
+
|
21
|
+
EmptyDefinition = Class.new(InvalidDefinition)
|
22
|
+
MissingCreateStatement = Class.new(InvalidDefinition)
|
23
|
+
ExtraDDL = Class.new(InvalidDefinition)
|
24
|
+
UnmatchedBracket = Class.new(InvalidDefinition)
|
25
|
+
UnclosedQuote = Class.new(InvalidDefinition)
|
26
|
+
UnmatchedComment = Class.new(InvalidDefinition)
|
27
|
+
MissingReturnType = Class.new(InvalidDefinition)
|
28
|
+
|
29
|
+
attr_accessor :name_prefix
|
30
|
+
|
31
|
+
attr_reader :command, :name, :type, :definition, :version, :return_type, :source_location
|
32
|
+
|
33
|
+
def initialize(raw_sql, source = '', default_timestamp = 0)
|
34
|
+
|
35
|
+
@source_location = source.to_s
|
36
|
+
@return_type = :table # the default. functions can be different. procedures can be iffy since they may or may not return a result set.
|
37
|
+
@command = 'CREATE'
|
38
|
+
|
39
|
+
if default_timestamp.is_a?(String)
|
40
|
+
default_timestamp = Time.utc_parse(default_timestamp)
|
41
|
+
end
|
42
|
+
|
43
|
+
if default_timestamp.is_a?(Date)
|
44
|
+
default_timestamp = (default_timestamp.strftime('%Y%m%d') + '0000').to_i
|
45
|
+
elsif default_timestamp.is_a?(Time)
|
46
|
+
default_timestamp = "#{default_timestamp.year.to_s.rjust(4,'0')}#{default_timestamp.month.to_s.rjust(2,'0')}#{default_timestamp.day.to_s.rjust(2,'0')}#{default_timestamp.hour.to_s.rjust(2,'0')}#{default_timestamp.min.to_s.rjust(2,'0')}".to_i
|
47
|
+
end
|
48
|
+
|
49
|
+
default_timestamp = 0 unless default_timestamp.is_a?(Fixnum)
|
50
|
+
|
51
|
+
ts_regex = /^--\s*(?<YR>\d{4})-?(?<MON>\d{2})-?(?<DAY>\d{2})\s*(?:(?<HR>\d{2}):?(?<MIN>\d{2})?)?\s*$/
|
52
|
+
|
53
|
+
timestamp = nil
|
54
|
+
|
55
|
+
raw_sql = raw_sql.to_s.lstrip
|
56
|
+
# strip leading comment lines.
|
57
|
+
while raw_sql[0...2] == '--' || raw_sql[0...2] == '/*'
|
58
|
+
if raw_sql[0...2] == '--'
|
59
|
+
# trim off the first line.
|
60
|
+
first_line,_,raw_sql = raw_sql.partition("\n").map {|v| v.strip}
|
61
|
+
raw_sql ||= ''
|
62
|
+
unless timestamp
|
63
|
+
if (ts = ts_regex.match(first_line))
|
64
|
+
timestamp = "#{ts['YR']}#{ts['MON']}#{ts['DAY']}#{ts['HR'] || '00'}#{ts['MIN'] || '00'}".to_i
|
65
|
+
end
|
66
|
+
end
|
67
|
+
else
|
68
|
+
# find the first */ sequence in the string.
|
69
|
+
comment_end = raw_sql.index('*/')
|
70
|
+
raise UnmatchedComment, 'raw_sql starts with "/*" sequence with no matching "*/" sequence' unless comment_end
|
71
|
+
|
72
|
+
# find the last /* sequence before that.
|
73
|
+
comment_start = raw_sql.rindex('/*', comment_end)
|
74
|
+
|
75
|
+
# remove this comment
|
76
|
+
raw_sql = (raw_sql[0...comment_start].to_s + raw_sql[(comment_end + 2)..-1].to_s).lstrip
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
timestamp ||= default_timestamp
|
81
|
+
|
82
|
+
raise EmptyDefinition, 'raw_sql contains no data' if raw_sql.blank?
|
83
|
+
|
84
|
+
# first line should be CREATE VIEW|FUNCTION|PROCEDURE "XYZ"
|
85
|
+
# or ALTER TABLE "XYZ"
|
86
|
+
regex = /^(?:(?<CMD>ALTER)\s+(?<TYPE>TABLE)|(?<CMD>CREATE)\s+(?<TYPE>TABLE|VIEW|FUNCTION|PROCEDURE))\s+["\[]?(?<NAME>[A-Z][A-Z0-9_]*)["\]]?\s+(?<DEFINITION>.*)$/im
|
87
|
+
match = regex.match(raw_sql)
|
88
|
+
|
89
|
+
raise MissingCreateStatement, 'raw_sql must contain a "CREATE|ALTER VIEW|FUNCTION|PROCEDURE" statement' unless match
|
90
|
+
|
91
|
+
@command = match['CMD'].upcase
|
92
|
+
@type = match['TYPE'].upcase
|
93
|
+
@name = match['NAME']
|
94
|
+
@definition = match['DEFINITION'].strip
|
95
|
+
|
96
|
+
# we're going to test the definition loosely.
|
97
|
+
# so first we'll get rid of all valid literals and comments.
|
98
|
+
# this will leave behind mangled invalid SQL, but we can use it to determine if there are any simple issues.
|
99
|
+
# all removed components are replaced by single spaces to ensure that the remaining components are separate from
|
100
|
+
# each other.
|
101
|
+
test_sql = match['DEFINITION']
|
102
|
+
.gsub(/\s+/,' ') # condense whitespace
|
103
|
+
.split(/(?:(?:'[^']*')|(?:"[^"]*")|(?:\[[^\[\]]*\]))/m).join(' ') # remove all quoted literals '', "", and []
|
104
|
+
.split(/--[^\r\n]*/).join(' ') # remove all single-line comments
|
105
|
+
|
106
|
+
# remove all multi-line comments
|
107
|
+
# the regex will find matches for all of the innermost multi-line comments.
|
108
|
+
regex = /\/\*(?:(?:[^\/\*])|(?:\/[^\*]))*\*\//m
|
109
|
+
|
110
|
+
# so we go through removing them until there are no longer any matches.
|
111
|
+
while regex.match(test_sql)
|
112
|
+
test_sql = test_sql.split(regex).join(' ')
|
113
|
+
end
|
114
|
+
|
115
|
+
# now we can test for a number of missing items nice and easily.
|
116
|
+
raise UnmatchedBracket, 'raw_sql contains an opening bracket with no closing bracket' if test_sql.include?('[')
|
117
|
+
raise UnmatchedBracket, 'raw_sql contains a closing bracket with no opening bracket' if test_sql.include?(']')
|
118
|
+
raise UnclosedQuote, 'raw_sql contains an unclosed string literal' if test_sql.include?("'")
|
119
|
+
raise UnclosedQuote, 'raw_sql contains an unclosed ANSI quoted literal' if test_sql.include?('"')
|
120
|
+
raise UnmatchedComment, 'raw_sql contains a "/*" sequence with no matching "*/" sequence' if test_sql.include?('/*')
|
121
|
+
raise UnmatchedComment, 'raw_sql contains a "*/" sequence with no matching "/*" sequence' if test_sql.include?('*/')
|
122
|
+
|
123
|
+
unless type == 'PROCEDURE'
|
124
|
+
# and finally we can test for extra DDL.
|
125
|
+
# only the initial CREATE statement is allowed.
|
126
|
+
regex = /\s(create|alter|drop|grant)\s/im
|
127
|
+
if (match = regex.match(test_sql))
|
128
|
+
raise ExtraDDL, "raw_sql contains a #{match[1]} statement after the initial CREATE statement"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# and for functions, get the return type.
|
133
|
+
if type == 'FUNCTION'
|
134
|
+
regex = /\sRETURNS\s+(?:@[A-Z][A-Z0-9_]*\s+)?(?<TYPE>[A-Z][A-Z0-9_()]*)\s/im
|
135
|
+
|
136
|
+
match = regex.match(@definition)
|
137
|
+
raise MissingReturnType, 'raw_sql seems to be missing the RETURNS statement for the function.' unless match
|
138
|
+
|
139
|
+
rtype = match['TYPE'].downcase
|
140
|
+
rsize = 0
|
141
|
+
if rtype.include?('(')
|
142
|
+
rtype,_,rsize = rtype.partition('(')
|
143
|
+
rsize = rsize.partition(')')[0].to_i
|
144
|
+
end
|
145
|
+
|
146
|
+
@return_type =
|
147
|
+
case rtype
|
148
|
+
when 'bit'
|
149
|
+
:boolean
|
150
|
+
|
151
|
+
when 'int', 'integer', 'bigint', 'smallint', 'tinyint'
|
152
|
+
:integer
|
153
|
+
|
154
|
+
when 'decimal', 'numeric', 'money', 'smallmoney'
|
155
|
+
:decimal
|
156
|
+
|
157
|
+
when 'float', 'real'
|
158
|
+
:float
|
159
|
+
|
160
|
+
when 'date', 'datetime', 'datetime2', 'time', 'smalldatetime', 'datetimeoffset'
|
161
|
+
:time
|
162
|
+
|
163
|
+
when 'char', 'varchar', 'text', 'nchar', 'nvarchar', 'ntext', 'binary', 'varbinary', 'image'
|
164
|
+
:string
|
165
|
+
|
166
|
+
else
|
167
|
+
rtype.to_sym
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# set the version.
|
172
|
+
@version = timestamp.to_s.ljust(12, '0') + Zlib.crc32(@definition).to_s(16).ljust(8,'0').upcase
|
173
|
+
end
|
174
|
+
|
175
|
+
def prefixed_name
|
176
|
+
prefix = name_prefix.to_s
|
177
|
+
return name if prefix == ''
|
178
|
+
return name if name.index(prefix) == 0
|
179
|
+
prefix + name
|
180
|
+
end
|
181
|
+
|
182
|
+
def is_create?
|
183
|
+
command == 'CREATE'
|
184
|
+
end
|
185
|
+
|
186
|
+
def update_sql
|
187
|
+
"#{command} #{type} \"#{prefixed_name}\"\n#{definition.gsub('@Z~',name_prefix)}"
|
188
|
+
end
|
189
|
+
|
190
|
+
def drop_sql
|
191
|
+
"DROP #{type} \"#{prefixed_name}\""
|
192
|
+
end
|
193
|
+
|
194
|
+
def grant_sql(user_name)
|
195
|
+
sel_exec = if type == 'PROCEDURE'
|
196
|
+
'EXECUTE'
|
197
|
+
elsif type == 'FUNCTION' && return_type != :table
|
198
|
+
'EXECUTE'
|
199
|
+
elsif type == 'TABLE'
|
200
|
+
'SELECT, INSERT, UPDATE, DELETE'
|
201
|
+
else
|
202
|
+
'SELECT'
|
203
|
+
end
|
204
|
+
|
205
|
+
"GRANT VIEW DEFINITION,#{sel_exec} ON \"#{prefixed_name}\" TO \"#{user_name}\""
|
206
|
+
end
|
207
|
+
|
208
|
+
def to_s
|
209
|
+
"#{type} #{name}"
|
210
|
+
end
|
211
|
+
|
212
|
+
def ==(other)
|
213
|
+
return false unless other.is_a?(MsSqlDefinition)
|
214
|
+
return false unless other.name == name
|
215
|
+
return false unless other.type == type
|
216
|
+
return false unless other.definition == definition
|
217
|
+
true
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,423 @@
|
|
1
|
+
module BarkestCore
|
2
|
+
|
3
|
+
##
|
4
|
+
# This class provides a model-like interface to SQL Server User Defined Functions.
|
5
|
+
#
|
6
|
+
# It's understandable that in terms of separation of concerns, logic has no place in the database.
|
7
|
+
# In the SQL Server world, UDFs cannot make changes to data, they can only present it.
|
8
|
+
# With that in mind, I consider UDFs to be parameterized queries, that are often times orders of magnitude
|
9
|
+
# faster than trying to construct a query via ActiveRecord.
|
10
|
+
#
|
11
|
+
# Although "models" inheriting from this class are not ActiveRecord models, this class does include
|
12
|
+
# ActiveModel::Model and ActiveModel::Validations to allow you to construct your UDF models with similar attributes
|
13
|
+
# to ActiveRecord models. For instance, you can ensure that returned values meet certain requirements using the
|
14
|
+
# validations. This allows you to further remove logic from the database and still gain the benefit of running
|
15
|
+
# a parameterized query.
|
16
|
+
class MsSqlFunction
|
17
|
+
|
18
|
+
InvalidConnection = Class.new(StandardError)
|
19
|
+
|
20
|
+
include ActiveModel::Model
|
21
|
+
include ActiveModel::Validations
|
22
|
+
|
23
|
+
include DateParser
|
24
|
+
include NumberParser
|
25
|
+
include BooleanParser
|
26
|
+
|
27
|
+
##
|
28
|
+
# Sets the connection handler to use for this function.
|
29
|
+
#
|
30
|
+
# The default behavior is to piggyback on the ActiveRecord::Base connections.
|
31
|
+
# To override this behavior, provide a class that responds to the :connection method.
|
32
|
+
#
|
33
|
+
# class MyFunction < MsSqlFunction
|
34
|
+
# use_connection SomeMsSqlTable
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
def self.use_connection(connected_object)
|
38
|
+
@conn_handler =
|
39
|
+
if connected_object.is_a?(Class)
|
40
|
+
connected_object
|
41
|
+
else
|
42
|
+
const_get(connected_object || 'ActiveRecord::Base')
|
43
|
+
end
|
44
|
+
raise ArgumentError.new('Connected object must be a class or class name.') unless @conn_handler
|
45
|
+
raise ArgumentError.new('Connected object must respond to :connection') unless @conn_handler.respond_to?(:connection)
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Gets a connection from the connection handler.
|
50
|
+
#
|
51
|
+
# The connection must be to a SQL Server since this class has no idea how to work with UDFs in any other language at this time.
|
52
|
+
#
|
53
|
+
def self.connection
|
54
|
+
conn = connection_handler.connection
|
55
|
+
raise InvalidConnection unless conn.is_a?(ActiveRecord::ConnectionAdapters::SQLServerAdapter)
|
56
|
+
conn
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Gets the UDF name for this class.
|
61
|
+
#
|
62
|
+
# It is important that you do not set this on the MsSqlFunction class itself.
|
63
|
+
#
|
64
|
+
def self.function_name
|
65
|
+
return '(none)' if self == MsSqlFunction
|
66
|
+
@udf ||= ''
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Sets the UDF name for this class.
|
71
|
+
#
|
72
|
+
# It is important that you do not set this on the MsSqlFunction class itself.
|
73
|
+
#
|
74
|
+
def self.function_name=(value)
|
75
|
+
raise StandardError.new("Function name for #{self} cannot be set.") if self == MsSqlFunction
|
76
|
+
raise StandardError.new("Function name for #{self} cannot be set more than once.") unless function_name.blank?
|
77
|
+
@udf = process_udf(value)
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Returns parameter information for the UDF.
|
82
|
+
#
|
83
|
+
# The returned hash contains the most important attributes for most applications including :type, :data_type, and :default.
|
84
|
+
#
|
85
|
+
def self.parameters
|
86
|
+
@param_info.inject({}) { |memo,(k,v)| memo[k] = { type: v[:type], data_type: v[:data_type], default: v[:default] }; memo }
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Sets the default values for parameters.
|
91
|
+
#
|
92
|
+
# The +values+ should be a hash using the parameter name as the key and the default as the value.
|
93
|
+
# The easiest way to ensure it works is to set the defaults in the hash returned from #parameters.
|
94
|
+
#
|
95
|
+
def self.parameters=(values)
|
96
|
+
if values && values.is_a?(Hash)
|
97
|
+
values.each do |k,v|
|
98
|
+
if @param_info[k]
|
99
|
+
if v.is_a?(Hash)
|
100
|
+
@param_info[k][:default] = v[:default]
|
101
|
+
else
|
102
|
+
@param_info[k][:default] = v
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# Gets the column information for the UDF.
|
111
|
+
def self.columns
|
112
|
+
@column_info
|
113
|
+
end
|
114
|
+
|
115
|
+
##
|
116
|
+
# Selects the data from the UDF using the provided parameters.
|
117
|
+
#
|
118
|
+
# MyFunction.select(user: 'john', day_of_week: 3)
|
119
|
+
#
|
120
|
+
# Returns an array containing the rows returned.
|
121
|
+
#
|
122
|
+
def self.select(params = {})
|
123
|
+
|
124
|
+
args = []
|
125
|
+
|
126
|
+
params = {} unless params.is_a?(Hash)
|
127
|
+
|
128
|
+
@param_info.each do |k,v|
|
129
|
+
args[v[:ordinal]] = [ v[:data_type], self.send(v[:format], params[k] || v[:default]) ]
|
130
|
+
end
|
131
|
+
|
132
|
+
where = ''
|
133
|
+
idx = args.count
|
134
|
+
params.each do |k,v|
|
135
|
+
unless @param_info.include? k
|
136
|
+
where += ' AND ' unless where.blank?
|
137
|
+
where += "([#{k}]"
|
138
|
+
if v.is_a? Array
|
139
|
+
# IN clause
|
140
|
+
where += ' IN (' + v.map{ |value| quote_param(value)[0] }.join(', ') + ')'
|
141
|
+
elsif v.is_a? Hash
|
142
|
+
if v.include? :between
|
143
|
+
v = v[:between]
|
144
|
+
raise ArgumentError.new("between clause for #{k} requires an array argument") unless v.is_a? Array
|
145
|
+
where += " BETWEEN @#{idx} AND @#{idx + 1}"
|
146
|
+
value,type = quote_param(v[0])
|
147
|
+
args[idx] = [ type, value ]
|
148
|
+
value,type = quote_param(v[1])
|
149
|
+
args[idx + 1] = [ type, value ]
|
150
|
+
idx += 2
|
151
|
+
elsif v.include? :like
|
152
|
+
where += " LIKE @#{idx}"
|
153
|
+
value,type = quote_param(v[:like].to_s)
|
154
|
+
args[idx] = [ type, value ]
|
155
|
+
idx += 1
|
156
|
+
else
|
157
|
+
operator = nil
|
158
|
+
value = nil
|
159
|
+
{ not: '<>', lt: '<', lte: '<=', gt: '>', gte: '>=', eq: '=' }.each do |key,op|
|
160
|
+
if v.include? key
|
161
|
+
operator = op
|
162
|
+
value = v[key]
|
163
|
+
break
|
164
|
+
end
|
165
|
+
end
|
166
|
+
raise ArgumentError.new("unknown clause for #{k}") unless operator
|
167
|
+
where += " #{operator} @#{idx}"
|
168
|
+
value,type = quote_param(value)
|
169
|
+
args[idx] = [ type, value ]
|
170
|
+
idx += 1
|
171
|
+
end
|
172
|
+
else
|
173
|
+
where += " = @#{idx}"
|
174
|
+
value,type = quote_param(v)
|
175
|
+
args[idx] = [ type, value ]
|
176
|
+
idx += 1
|
177
|
+
end
|
178
|
+
where += ')'
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
sql = "SELECT * FROM #{@udf}(#{@udf_args})"
|
183
|
+
sql += " WHERE #{where}" unless where.blank?
|
184
|
+
|
185
|
+
ret = []
|
186
|
+
|
187
|
+
execute(sql, args) do |row|
|
188
|
+
ret << self.new(row)
|
189
|
+
end
|
190
|
+
|
191
|
+
ret
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
def self.quote_param(value)
|
198
|
+
if value.nil?
|
199
|
+
[ 'NULL', 'varchar(1)' ]
|
200
|
+
elsif value.is_a? Integer
|
201
|
+
[ value.to_s, 'integer' ]
|
202
|
+
elsif value.is_a? Float
|
203
|
+
[ value.to_s, 'float' ]
|
204
|
+
elsif value.is_a?(Date) || value.is_a?(Time)
|
205
|
+
[ value.strftime('%Y-%m-%d %H:%M:%S'), 'datetime' ]
|
206
|
+
elsif value.is_a? TrueClass
|
207
|
+
[ 1, 'bit' ]
|
208
|
+
elsif value.is_a? FalseClass
|
209
|
+
[ 0, 'bit' ]
|
210
|
+
else
|
211
|
+
[ "'#{value.to_s.gsub('\'','\'\'')}'", 'varchar(max)' ]
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.instrumenter
|
216
|
+
@instrumenter ||= ActiveSupport::Notifications.instrumenter
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
def self.execute(sql, binds)
|
221
|
+
sql = "EXEC sp_executesql N'#{sql.gsub('\'','\'\'')}'"
|
222
|
+
|
223
|
+
unless binds.blank?
|
224
|
+
binds.each_with_index do |v,i|
|
225
|
+
sql += i == 0 ? ', N\'' : ', '
|
226
|
+
sql += "@#{i} #{v[0]}"
|
227
|
+
end
|
228
|
+
sql += '\''
|
229
|
+
binds.each_with_index do |v,i|
|
230
|
+
sql += ", @#{i}=#{v[1]}"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
ret = []
|
235
|
+
|
236
|
+
conn = connection
|
237
|
+
instrumenter.instrument(
|
238
|
+
"sql.active_record",
|
239
|
+
:sql => sql,
|
240
|
+
:name => 'SQL',
|
241
|
+
:connection_id => conn.object_id,
|
242
|
+
:statement_name => nil,
|
243
|
+
:binds => nil) do
|
244
|
+
conn.instance_variable_get("@connection").execute(sql).each(as: :hash) do |row|
|
245
|
+
ret << row
|
246
|
+
yield row if block_given?
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
ret
|
251
|
+
end
|
252
|
+
|
253
|
+
|
254
|
+
def self.parse_for_string_filter(value)
|
255
|
+
value.nil? ? 'NULL' : "'#{value.to_s.gsub('\'','\'\'')}'"
|
256
|
+
end
|
257
|
+
|
258
|
+
def parse_for_string_filter(value)
|
259
|
+
self.class.parse_for_string_filter value
|
260
|
+
end
|
261
|
+
|
262
|
+
def self.connection_handler
|
263
|
+
@conn_handler ||= const_get('ActiveRecord::Base')
|
264
|
+
end
|
265
|
+
|
266
|
+
def self.get_udf_definition(name)
|
267
|
+
execute('SELECT R.ROUTINE_CATALOG AS [catalog], R.ROUTINE_SCHEMA AS [schema], R.ROUTINE_NAME AS [name], ' +
|
268
|
+
'R.ROUTINE_DEFINITION AS [definition] FROM INFORMATION_SCHEMA.ROUTINES R WHERE ' +
|
269
|
+
'R.ROUTINE_TYPE=\'FUNCTION\' AND R.DATA_TYPE=\'TABLE\' AND R.ROUTINE_NAME=@0',
|
270
|
+
[
|
271
|
+
['varchar(100)', parse_for_string_filter(name)]
|
272
|
+
]
|
273
|
+
) do |row|
|
274
|
+
return [ row['catalog'], row['schema'], row['name'], row['definition'] ]
|
275
|
+
end
|
276
|
+
[ nil, nil, nil, nil ]
|
277
|
+
end
|
278
|
+
|
279
|
+
|
280
|
+
def self.get_udf_params(sql_def)
|
281
|
+
# get everything before the return definition
|
282
|
+
# should be something like "CREATE FUNCTION xyz (a type, b type)"
|
283
|
+
sql_def = sql_def.split(/\sreturn/i)[0]
|
284
|
+
|
285
|
+
ret = {}
|
286
|
+
|
287
|
+
if sql_def['(']
|
288
|
+
param_defs = sql_def.split('(', 2)[1].rpartition(')')[0].split(',').map{|d| d.strip}
|
289
|
+
|
290
|
+
param_defs.each_with_index do |raw,idx|
|
291
|
+
pname,pdatatype = raw.split(' ')
|
292
|
+
|
293
|
+
psym = pname
|
294
|
+
if psym[0] == '@'
|
295
|
+
psym = psym[1..-1]
|
296
|
+
end
|
297
|
+
psym = psym.underscore.to_sym
|
298
|
+
|
299
|
+
pdatatype.downcase!
|
300
|
+
|
301
|
+
if pdatatype.include? 'date'
|
302
|
+
pfmt = :parse_for_date_filter
|
303
|
+
ptype = :datetime
|
304
|
+
elsif pdatatype.include? 'float'
|
305
|
+
pfmt = :parse_for_float_filter
|
306
|
+
ptype = :float
|
307
|
+
elsif pdatatype.include? 'int'
|
308
|
+
pfmt = :parse_for_int_filter
|
309
|
+
ptype = :integer
|
310
|
+
elsif pdatatype.include? 'bit'
|
311
|
+
pfmt = :parse_for_boolean_filter
|
312
|
+
ptype = :boolean
|
313
|
+
else
|
314
|
+
pfmt = :parse_for_string_filter
|
315
|
+
ptype = :string
|
316
|
+
end
|
317
|
+
|
318
|
+
ret[psym] = { name: pname, data_type: pdatatype, type: ptype, format: pfmt, ordinal: idx }
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
ret
|
323
|
+
end
|
324
|
+
|
325
|
+
|
326
|
+
def self.get_udf_columns(catalog, schema, name)
|
327
|
+
execute('SELECT C.COLUMN_NAME AS [name], C.IS_NULLABLE AS [nullable], C.DATA_TYPE AS [type], ' +
|
328
|
+
'C.CHARACTER_MAXIMUM_LENGTH AS [length], C.ORDINAL_POSITION AS [ordinal] ' +
|
329
|
+
'FROM INFORMATION_SCHEMA.ROUTINE_COLUMNS C ' +
|
330
|
+
'WHERE C.TABLE_CATALOG=@0 AND C.TABLE_SCHEMA=@1 AND C.TABLE_NAME=@2 ' +
|
331
|
+
'ORDER BY C.ORDINAL_POSITION',
|
332
|
+
[
|
333
|
+
['varchar(100)', parse_for_string_filter(catalog)],
|
334
|
+
['varchar(100)', parse_for_string_filter(schema)],
|
335
|
+
['varchar(100)', parse_for_string_filter(name)]
|
336
|
+
]
|
337
|
+
) do |row|
|
338
|
+
yield row if block_given?
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
|
343
|
+
def self.process_udf(name)
|
344
|
+
catalog, schema, name, sql_def = get_udf_definition(name)
|
345
|
+
|
346
|
+
raise StandardError.new("The specified function '#{name.to_s.gsub('\'','\'\'')}' could not be defined.") if sql_def.blank?
|
347
|
+
|
348
|
+
@param_info = get_udf_params(sql_def)
|
349
|
+
|
350
|
+
if @param_info.blank?
|
351
|
+
@udf_args = ''
|
352
|
+
else
|
353
|
+
@udf_args = @param_info.map.with_index { |v,i| "@#{i}" }.join(', ')
|
354
|
+
end
|
355
|
+
|
356
|
+
@column_info = [ ]
|
357
|
+
|
358
|
+
get_udf_columns(catalog, schema, name) do |column|
|
359
|
+
col_key = column['name'].underscore.to_sym
|
360
|
+
getter = col_key
|
361
|
+
setter = "#{col_key}="
|
362
|
+
|
363
|
+
col_info = { name: column['name'], key: col_key, ordinal: column['ordinal'], nullable: true, length: -1 }
|
364
|
+
|
365
|
+
type = column['type'].downcase
|
366
|
+
|
367
|
+
unless column['length'].blank?
|
368
|
+
type += "(#{column['length']})"
|
369
|
+
col_info[:length] = column['length'].to_i
|
370
|
+
end
|
371
|
+
|
372
|
+
col_info[:data_type] = type
|
373
|
+
|
374
|
+
attr_reader col_key
|
375
|
+
|
376
|
+
if type == 'int' || type == 'integer'
|
377
|
+
define_method setter do |value|
|
378
|
+
instance_variable_set "@#{col_key}", parse_for_int_column(value)
|
379
|
+
end
|
380
|
+
col_info[:type] = :integer
|
381
|
+
elsif type == 'float'
|
382
|
+
define_method setter do |value|
|
383
|
+
instance_variable_set "@#{col_key}", parse_for_float_column(value)
|
384
|
+
end
|
385
|
+
col_info[:type] = :float
|
386
|
+
elsif type == 'date' || (type == 'datetime' && col_key.to_s.include?('date'))
|
387
|
+
define_method setter do |value|
|
388
|
+
instance_variable_set "@#{col_key}", parse_for_date_column(value)
|
389
|
+
end
|
390
|
+
col_info[:type] = :datetime
|
391
|
+
elsif type == 'datetime'
|
392
|
+
define_method setter do |value|
|
393
|
+
instance_variable_set "@#{col_key}", parse_for_time_column(value)
|
394
|
+
end
|
395
|
+
col_info[:type] = :datetime
|
396
|
+
elsif type == 'bit'
|
397
|
+
define_method setter do |value|
|
398
|
+
instance_variable_set "@#{col_key}", parse_for_boolean_column(value)
|
399
|
+
end
|
400
|
+
define_method "#{getter}?" do
|
401
|
+
instance_variable_get "@#{col_key}"
|
402
|
+
end
|
403
|
+
col_info[:type] = :boolean
|
404
|
+
else
|
405
|
+
define_method setter do |value|
|
406
|
+
instance_variable_set "@#{col_key}", value.nil? ? nil : value.to_s
|
407
|
+
end
|
408
|
+
col_info[:type] = :string
|
409
|
+
end
|
410
|
+
|
411
|
+
if column['nullable'].upcase == 'NO'
|
412
|
+
validates col_key, presence: true
|
413
|
+
col_info[:nullable] = false
|
414
|
+
end
|
415
|
+
|
416
|
+
@column_info << col_info
|
417
|
+
end
|
418
|
+
|
419
|
+
"[#{schema}].[#{name}]"
|
420
|
+
end
|
421
|
+
|
422
|
+
end
|
423
|
+
end
|