barkest_core 1.5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|