mxrb 0.1.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/LICENSE +21 -0
- data/README.de-DE.md +108 -0
- data/README.md +172 -0
- data/README.pt-BR.md +109 -0
- data/bin/mxrb +1613 -0
- data/docs/README.md +10 -0
- data/docs/de-DE/README.md +24 -0
- data/docs/de-DE/architectural-patterns.md +26 -0
- data/docs/de-DE/architectural-standard.md +48 -0
- data/docs/de-DE/architecture.md +76 -0
- data/docs/de-DE/compiler.md +162 -0
- data/docs/de-DE/conventions.md +19 -0
- data/docs/de-DE/design-system.md +52 -0
- data/docs/de-DE/entity-dsl.md +53 -0
- data/docs/de-DE/native-runtime-quality-report.md +74 -0
- data/docs/de-DE/oql-sql.md +211 -0
- data/docs/de-DE/platform-api-integration.md +36 -0
- data/docs/de-DE/platform-operations.md +243 -0
- data/docs/de-DE/project-structure.md +32 -0
- data/docs/de-DE/ruby-first-roadmap.md +116 -0
- data/docs/de-DE/scaffolds.md +59 -0
- data/docs/de-DE/semantic-refactoring.md +56 -0
- data/docs/de-DE/team-server.md +33 -0
- data/docs/de-DE/validation-matrix.md +64 -0
- data/docs/de-DE/vetclinic-acceptance.md +24 -0
- data/docs/de-DE/writing.md +138 -0
- data/docs/en-US/README.md +24 -0
- data/docs/en-US/architectural-patterns.md +151 -0
- data/docs/en-US/architectural-standard.md +89 -0
- data/docs/en-US/architecture.md +170 -0
- data/docs/en-US/compiler.md +157 -0
- data/docs/en-US/conventions.md +82 -0
- data/docs/en-US/design-system.md +163 -0
- data/docs/en-US/entity-dsl.md +53 -0
- data/docs/en-US/native-runtime-quality-report.md +73 -0
- data/docs/en-US/oql-sql.md +201 -0
- data/docs/en-US/platform-api-integration.md +36 -0
- data/docs/en-US/platform-operations.md +244 -0
- data/docs/en-US/project-structure.md +49 -0
- data/docs/en-US/ruby-first-roadmap.md +186 -0
- data/docs/en-US/scaffolds.md +57 -0
- data/docs/en-US/semantic-refactoring.md +198 -0
- data/docs/en-US/team-server.md +32 -0
- data/docs/en-US/validation-matrix.md +257 -0
- data/docs/en-US/vetclinic-acceptance.md +23 -0
- data/docs/en-US/writing.md +655 -0
- data/docs/pt-BR/README.md +24 -0
- data/docs/pt-BR/architectural-patterns.md +25 -0
- data/docs/pt-BR/architectural-standard.md +533 -0
- data/docs/pt-BR/architecture.md +300 -0
- data/docs/pt-BR/compiler.md +162 -0
- data/docs/pt-BR/conventions.md +19 -0
- data/docs/pt-BR/design-system.md +65 -0
- data/docs/pt-BR/entity-dsl.md +116 -0
- data/docs/pt-BR/native-runtime-quality-report.md +73 -0
- data/docs/pt-BR/oql-sql.md +208 -0
- data/docs/pt-BR/platform-api-integration.md +36 -0
- data/docs/pt-BR/platform-operations.md +260 -0
- data/docs/pt-BR/project-structure.md +32 -0
- data/docs/pt-BR/ruby-first-roadmap.md +239 -0
- data/docs/pt-BR/scaffolds.md +104 -0
- data/docs/pt-BR/semantic-refactoring.md +55 -0
- data/docs/pt-BR/team-server.md +59 -0
- data/docs/pt-BR/validation-matrix.md +86 -0
- data/docs/pt-BR/vetclinic-acceptance.md +50 -0
- data/docs/pt-BR/writing.md +177 -0
- data/examples/sudoku_evaluation.rb +14 -0
- data/examples/sudoku_functional_test.rb +28 -0
- data/lib/mxrb/architecture/graph.rb +124 -0
- data/lib/mxrb/architecture/validator.rb +98 -0
- data/lib/mxrb/benchmark.rb +33 -0
- data/lib/mxrb/compare.rb +390 -0
- data/lib/mxrb/compiler/adapter.rb +96 -0
- data/lib/mxrb/compiler/artifact_document_compiler.rb +102 -0
- data/lib/mxrb/compiler/artifact_materializer.rb +53 -0
- data/lib/mxrb/compiler/client_model_materializer.rb +42 -0
- data/lib/mxrb/compiler/code_action_document_compiler.rb +64 -0
- data/lib/mxrb/compiler/code_action_materializer.rb +35 -0
- data/lib/mxrb/compiler/code_action_type_compiler.rb +32 -0
- data/lib/mxrb/compiler/combo_box_bundle_compiler.rb +314 -0
- data/lib/mxrb/compiler/compatibility_analyzer.rb +169 -0
- data/lib/mxrb/compiler/constants_materializer.rb +111 -0
- data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +290 -0
- data/lib/mxrb/compiler/database_connector_action_compiler.rb +280 -0
- data/lib/mxrb/compiler/deployment_asset_copier.rb +60 -0
- data/lib/mxrb/compiler/deployment_bootstrapper.rb +208 -0
- data/lib/mxrb/compiler/deployment_materializer.rb +43 -0
- data/lib/mxrb/compiler/deployment_metadata.rb +64 -0
- data/lib/mxrb/compiler/domain_document_compiler.rb +153 -0
- data/lib/mxrb/compiler/domain_model_materializer.rb +35 -0
- data/lib/mxrb/compiler/domain_security_compiler.rb +82 -0
- data/lib/mxrb/compiler/gallery_bundle_compiler.rb +190 -0
- data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +281 -0
- data/lib/mxrb/compiler/image_bundle_compiler.rb +216 -0
- data/lib/mxrb/compiler/java_proxy_generator.rb +509 -0
- data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +267 -0
- data/lib/mxrb/compiler/legacy_page_builder.rb +287 -0
- data/lib/mxrb/compiler/mda.rb +99 -0
- data/lib/mxrb/compiler/microflow_document_compiler.rb +85 -0
- data/lib/mxrb/compiler/microflow_materializer.rb +48 -0
- data/lib/mxrb/compiler/microflow_node_compiler.rb +250 -0
- data/lib/mxrb/compiler/model_package.rb +116 -0
- data/lib/mxrb/compiler/model_values.rb +67 -0
- data/lib/mxrb/compiler/nanoflow_program_compiler.rb +483 -0
- data/lib/mxrb/compiler/navigation_document_compiler.rb +93 -0
- data/lib/mxrb/compiler/packager.rb +100 -0
- data/lib/mxrb/compiler/page_bundle_builder.rb +56 -0
- data/lib/mxrb/compiler/page_bundle_compiler.rb +2004 -0
- data/lib/mxrb/compiler/page_document_compiler.rb +77 -0
- data/lib/mxrb/compiler/portable_packager.rb +356 -0
- data/lib/mxrb/compiler/project_jar_archive.rb +84 -0
- data/lib/mxrb/compiler/project_jar_builder.rb +121 -0
- data/lib/mxrb/compiler/project_materializer.rb +139 -0
- data/lib/mxrb/compiler/project_model_orderer.rb +52 -0
- data/lib/mxrb/compiler/runtime_data_types.rb +31 -0
- data/lib/mxrb/compiler/runtime_model_schema.rb +90 -0
- data/lib/mxrb/compiler/schemas/runtime-10.24.0.73019.json +1052 -0
- data/lib/mxrb/compiler/schemas/runtime-11.json +1215 -0
- data/lib/mxrb/compiler/schemas/runtime-6.10.8.json +1148 -0
- data/lib/mxrb/compiler/schemas/runtime-7.17.0.json +1144 -0
- data/lib/mxrb/compiler/schemas/runtime-7.5.0.json +1215 -0
- data/lib/mxrb/compiler/schemas/runtime-9.6.1.29396.json +868 -0
- data/lib/mxrb/compiler/schemas/system-model-10.24.0.73019.b64 +1135 -0
- data/lib/mxrb/compiler/schemas/system-model-11.12.1.b64 +815 -0
- data/lib/mxrb/compiler/schemas/system-model-6.10.8.b64 +497 -0
- data/lib/mxrb/compiler/schemas/system-model-7.17.0.b64 +519 -0
- data/lib/mxrb/compiler/schemas/system-model-7.5.0.b64 +504 -0
- data/lib/mxrb/compiler/schemas/system-model-9.6.1.29396.b64 +816 -0
- data/lib/mxrb/compiler/security_materializer.rb +104 -0
- data/lib/mxrb/compiler/settings_document_compiler.rb +45 -0
- data/lib/mxrb/compiler/settings_materializer.rb +25 -0
- data/lib/mxrb/compiler/source_model.rb +128 -0
- data/lib/mxrb/compiler/system_model_seed.rb +88 -0
- data/lib/mxrb/compiler/system_queue_materializer.rb +54 -0
- data/lib/mxrb/compiler/system_text_materializer.rb +49 -0
- data/lib/mxrb/compiler/translation_materializer.rb +95 -0
- data/lib/mxrb/compiler/web_bundle_builder.rb +155 -0
- data/lib/mxrb/compiler/web_list_data_source.rb +139 -0
- data/lib/mxrb/compiler/web_operation_compiler.rb +614 -0
- data/lib/mxrb/compiler/web_shell_materializer.rb +194 -0
- data/lib/mxrb/compiler/widget_package_extractor.rb +63 -0
- data/lib/mxrb/doctor.rb +116 -0
- data/lib/mxrb/dsl/builder.rb +1636 -0
- data/lib/mxrb/errors.rb +18 -0
- data/lib/mxrb/evaluation.rb +131 -0
- data/lib/mxrb/exporter.rb +1716 -0
- data/lib/mxrb/frontend/migrator.rb +775 -0
- data/lib/mxrb/functional.rb +307 -0
- data/lib/mxrb/github/annotator.rb +161 -0
- data/lib/mxrb/initializer.rb +257 -0
- data/lib/mxrb/integrity/validator.rb +136 -0
- data/lib/mxrb/io/bson_codec.rb +145 -0
- data/lib/mxrb/io/mpr_file.rb +558 -0
- data/lib/mxrb/io/mxunit_codec.rb +42 -0
- data/lib/mxrb/marketplace.rb +380 -0
- data/lib/mxrb/model/association.rb +98 -0
- data/lib/mxrb/model/attribute.rb +100 -0
- data/lib/mxrb/model/connector.rb +17 -0
- data/lib/mxrb/model/design_materializer.rb +133 -0
- data/lib/mxrb/model/design_migration.rb +126 -0
- data/lib/mxrb/model/design_system.rb +111 -0
- data/lib/mxrb/model/domain_model.rb +51 -0
- data/lib/mxrb/model/entity.rb +180 -0
- data/lib/mxrb/model/menu.rb +38 -0
- data/lib/mxrb/model/microflow.rb +100 -0
- data/lib/mxrb/model/module.rb +142 -0
- data/lib/mxrb/model/navigation.rb +127 -0
- data/lib/mxrb/model/page.rb +410 -0
- data/lib/mxrb/model/project.rb +187 -0
- data/lib/mxrb/model/unit.rb +48 -0
- data/lib/mxrb/module_initializer.rb +75 -0
- data/lib/mxrb/official_marketplace/content_api.rb +292 -0
- data/lib/mxrb/official_marketplace/dependency_resolver.rb +329 -0
- data/lib/mxrb/official_marketplace/lifecycle.rb +367 -0
- data/lib/mxrb/official_marketplace/module_package_importer.rb +318 -0
- data/lib/mxrb/official_marketplace/widget_package_installer.rb +414 -0
- data/lib/mxrb/official_marketplace.rb +720 -0
- data/lib/mxrb/oql/analyzer.rb +155 -0
- data/lib/mxrb/oql/index_advisor.rb +85 -0
- data/lib/mxrb/oql/plan_analyzer.rb +185 -0
- data/lib/mxrb/oql/server.rb +132 -0
- data/lib/mxrb/oql/sql_server_plan_analyzer.rb +190 -0
- data/lib/mxrb/oql/sql_server_workload_analyzer.rb +80 -0
- data/lib/mxrb/oql/workload_analyzer.rb +175 -0
- data/lib/mxrb/oql/workload_baseline.rb +60 -0
- data/lib/mxrb/oql.rb +424 -0
- data/lib/mxrb/project_lifecycle.rb +76 -0
- data/lib/mxrb/protocols/plan.rb +86 -0
- data/lib/mxrb/protocols.rb +123 -0
- data/lib/mxrb/runtime/database_workspace.rb +677 -0
- data/lib/mxrb/runtime/docker_executor.rb +41 -0
- data/lib/mxrb/runtime/docker_workspace.rb +29 -0
- data/lib/mxrb/runtime/executor.rb +169 -0
- data/lib/mxrb/runtime/java_locator.rb +26 -0
- data/lib/mxrb/runtime/native.rb +474 -0
- data/lib/mxrb/runtime/sql_server_database.rb +146 -0
- data/lib/mxrb/runtime/toolchain.rb +82 -0
- data/lib/mxrb/scaffold/cli.rb +113 -0
- data/lib/mxrb/scaffold/generator.rb +246 -0
- data/lib/mxrb/scaffold/help.rb +80 -0
- data/lib/mxrb/scaffold/page_templates.rb +54 -0
- data/lib/mxrb/scaffold/recipes.rb +198 -0
- data/lib/mxrb/scaffold/registry.rb +82 -0
- data/lib/mxrb/scaffold/templates.rb +723 -0
- data/lib/mxrb/scaffold/transaction.rb +84 -0
- data/lib/mxrb/schema/tables.rb +55 -0
- data/lib/mxrb/semantic/analyzer.rb +473 -0
- data/lib/mxrb/semantic/batch_plan.rb +68 -0
- data/lib/mxrb/semantic/domain_mutator.rb +419 -0
- data/lib/mxrb/semantic/embedder.rb +51 -0
- data/lib/mxrb/semantic/extractor.rb +290 -0
- data/lib/mxrb/semantic/index.rb +623 -0
- data/lib/mxrb/semantic/inliner.rb +216 -0
- data/lib/mxrb/semantic/mover.rb +153 -0
- data/lib/mxrb/semantic/onnx_embedder.rb +34 -0
- data/lib/mxrb/semantic/remover.rb +70 -0
- data/lib/mxrb/semantic/renamer.rb +211 -0
- data/lib/mxrb/semantic/tfidf_embedder.rb +46 -0
- data/lib/mxrb/semantic/vec_store.rb +75 -0
- data/lib/mxrb/team_server.rb +390 -0
- data/lib/mxrb/templates/project/10.24.0.73019.json +32 -0
- data/lib/mxrb/templates/project/11.12.1.json +32 -0
- data/lib/mxrb/templates/project/6.10.8.json +50 -0
- data/lib/mxrb/templates/project/7.17.0.json +50 -0
- data/lib/mxrb/templates/project/7.5.0.json +50 -0
- data/lib/mxrb/templates/project/9.6.1.29396.json +50 -0
- data/lib/mxrb/version.rb +5 -0
- data/lib/mxrb/widget_package.rb +338 -0
- data/lib/mxrb/widget_synchronizer.rb +31 -0
- data/lib/mxrb/writer.rb +3775 -0
- data/lib/mxrb.rb +188 -0
- data/marketplace/catalog.json +10 -0
- data/marketplace/modules/shared-kernel/domain/README.md +4 -0
- data/marketplace/modules/shared-kernel/module.rb +5 -0
- data/marketplace/modules/shared-kernel/mxrb-module.json +9 -0
- metadata +447 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'tmpdir'
|
|
6
|
+
|
|
7
|
+
module Mxrb
|
|
8
|
+
module Compiler
|
|
9
|
+
ConstantsMaterialization = Data.define(:model_path, :metadata_path, :constants)
|
|
10
|
+
|
|
11
|
+
# Compiles MPR constants into Runtime model and metadata representations.
|
|
12
|
+
class ConstantsMaterializer
|
|
13
|
+
DATA_TYPES = {
|
|
14
|
+
'DataTypes$StringType' => 'String', 'DataTypes$IntegerType' => 'Integer',
|
|
15
|
+
'DataTypes$BooleanType' => 'Boolean', 'DataTypes$DecimalType' => 'Decimal',
|
|
16
|
+
'DataTypes$DateTimeType' => 'DateTime'
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
def initialize(mpr_path, deployment:)
|
|
20
|
+
@mpr_path = File.expand_path(mpr_path)
|
|
21
|
+
@deployment = File.expand_path(deployment)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def materialize
|
|
25
|
+
source = SourceModel.read(@mpr_path)
|
|
26
|
+
constants = compile(source)
|
|
27
|
+
model_path = File.join(@deployment, 'model', 'model.mdp')
|
|
28
|
+
metadata_path = File.join(@deployment, 'model', 'metadata.json')
|
|
29
|
+
write_model(model_path, constants)
|
|
30
|
+
metadata_constants = write_metadata(metadata_path, constants)
|
|
31
|
+
ConstantsMaterialization.new(
|
|
32
|
+
model_path:, metadata_path:, constants: metadata_constants.freeze
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def compile(source)
|
|
39
|
+
source.units_of('Constants$Constant').map do |unit|
|
|
40
|
+
raise CompilationError, "constant #{unit.document['Name']} is outside a module" unless unit.module_name
|
|
41
|
+
|
|
42
|
+
compile_constant(source, unit)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def compile_constant(source, unit)
|
|
47
|
+
document = unit.document
|
|
48
|
+
qualified_name = "#{unit.module_name}.#{document['Name']}"
|
|
49
|
+
data_type = constant_type(document, qualified_name)
|
|
50
|
+
{
|
|
51
|
+
module_name: unit.module_name, runtime: runtime_document(document, qualified_name, data_type, source),
|
|
52
|
+
metadata: metadata_document(document, qualified_name, data_type)
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def constant_type(document, qualified_name)
|
|
57
|
+
native = document.dig('Type', '$Type')
|
|
58
|
+
legacy = document['DataType'].to_s
|
|
59
|
+
return legacy if DATA_TYPES.value?(legacy)
|
|
60
|
+
|
|
61
|
+
DATA_TYPES.fetch(native) do
|
|
62
|
+
raise CompilationError, "unsupported constant type #{native.inspect} for #{qualified_name}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def runtime_document(source, qualified_name, data_type, model)
|
|
67
|
+
{
|
|
68
|
+
'$ID' => source['$ID'], '$Type' => source['$Type'], 'Name' => source['Name'],
|
|
69
|
+
'QualifiedName' => qualified_name, 'DataType' => data_type,
|
|
70
|
+
'ExposedToClient' => model.client_reference?(qualified_name)
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def metadata_document(source, qualified_name, data_type)
|
|
75
|
+
{
|
|
76
|
+
'Name' => qualified_name, 'Type' => data_type,
|
|
77
|
+
'Description' => source['Documentation'].to_s,
|
|
78
|
+
'DefaultValue' => source['DefaultValue'].to_s
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def write_metadata(path, constants)
|
|
83
|
+
metadata = JSON.parse(File.read(path))
|
|
84
|
+
metadata['Constants'] = ordered_metadata(constants)
|
|
85
|
+
atomic_json_write(path, metadata)
|
|
86
|
+
metadata.fetch('Constants')
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def ordered_metadata(constants)
|
|
90
|
+
constants.group_by { _1.fetch(:module_name) }
|
|
91
|
+
.flat_map { |_module, entries| entries.sort_by { _1.dig(:metadata, 'Name') } }
|
|
92
|
+
.map { _1.fetch(:metadata) }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def write_model(path, constants)
|
|
96
|
+
package = constants.reduce(ModelPackage.read(path)) do |result, constant|
|
|
97
|
+
result.upsert(constant.fetch(:runtime))
|
|
98
|
+
end
|
|
99
|
+
package.write(path)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def atomic_json_write(path, document)
|
|
103
|
+
Dir.mktmpdir('mxrb-metadata-', File.dirname(path)) do |directory|
|
|
104
|
+
temporary = File.join(directory, 'metadata.json')
|
|
105
|
+
File.write(temporary, JSON.pretty_generate(document))
|
|
106
|
+
FileUtils.mv(temporary, path, force: true)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Mxrb
|
|
6
|
+
module Compiler
|
|
7
|
+
# Compiles the Data Grid 2 XPath/attribute-column subset into pluggable-widget properties.
|
|
8
|
+
# rubocop:disable Metrics
|
|
9
|
+
class DataGridBundleCompiler
|
|
10
|
+
include ModelValues
|
|
11
|
+
|
|
12
|
+
WIDGET_ID = 'com.mendix.widget.web.datagrid.Datagrid'
|
|
13
|
+
|
|
14
|
+
def initialize(source, page_name, widget = nil, render_widgets: nil, **widget_keywords)
|
|
15
|
+
@source = source
|
|
16
|
+
@page_name = page_name
|
|
17
|
+
@widget = widget || widget_keywords
|
|
18
|
+
@render_widgets = render_widgets
|
|
19
|
+
@index = document_index
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def supported?
|
|
23
|
+
widget_type&.fetch('WidgetId', nil) == WIDGET_ID && xpath_source &&
|
|
24
|
+
columns.all? { supported_column?(_1) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def render
|
|
28
|
+
"React.createElement($Datagrid, #{js_object(properties)})"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def properties
|
|
34
|
+
values = property_values(@widget['Object'])
|
|
35
|
+
primitives(values).merge(
|
|
36
|
+
key: widget_key, '$widgetId': widget_key, advanced: false,
|
|
37
|
+
datasource: datasource, columns: columns.map { compile_column(_1) },
|
|
38
|
+
class: css_class
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def primitives(values)
|
|
43
|
+
values.each_with_object({}) do |(key, pair), result|
|
|
44
|
+
type, value = pair
|
|
45
|
+
compiled = primitive(type, value)
|
|
46
|
+
result[key.to_sym] = compiled unless compiled.equal?(:undefined)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def primitive(type, value) # rubocop:disable Metrics/CyclomaticComplexity
|
|
51
|
+
compiled = case type
|
|
52
|
+
when 'Boolean' then value['PrimitiveValue'] == 'true'
|
|
53
|
+
when 'Integer' then value['PrimitiveValue'].to_i
|
|
54
|
+
when 'Enumeration' then value['PrimitiveValue'].to_s
|
|
55
|
+
when 'TextTemplate' then expression(translated_text(value['TextTemplate']))
|
|
56
|
+
when 'Widgets' then [] if array(value['Widgets']).empty?
|
|
57
|
+
end
|
|
58
|
+
compiled.nil? ? :undefined : compiled
|
|
59
|
+
end # rubocop:enable Metrics/CyclomaticComplexity
|
|
60
|
+
|
|
61
|
+
def datasource
|
|
62
|
+
{
|
|
63
|
+
'$raw' => "DatabaseObjectListProperty(#{js_object(
|
|
64
|
+
dataSourceId: data_source_id, entity: entity_name,
|
|
65
|
+
operationId: WebOperationCompiler.operation_id(@page_name, @widget['Name']), sort: []
|
|
66
|
+
)})"
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def compile_column(object) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
71
|
+
values = property_values(object)
|
|
72
|
+
mode = values.fetch('showContentAs').last['PrimitiveValue']
|
|
73
|
+
attribute = attribute_name(values)
|
|
74
|
+
entity, name = attribute.rpartition('.').then { |prefix, _, suffix| [prefix, suffix] }
|
|
75
|
+
result = primitives(values).merge(
|
|
76
|
+
showContentAs: mode, attribute: attribute.empty? ? :undefined : raw_attribute(entity, name),
|
|
77
|
+
dynamicText: :undefined, header: expression(text_value(values['header']&.last)),
|
|
78
|
+
tooltip: :undefined, filter: compile_widgets(values['filter']&.last),
|
|
79
|
+
visible: expression(true), exportValue: :undefined
|
|
80
|
+
)
|
|
81
|
+
result[:dynamicText] = dynamic_text(values, entity, name) if mode == 'dynamicText'
|
|
82
|
+
result[:content] = templated_content(values) if mode == 'customContent'
|
|
83
|
+
result.merge!(association_filter(values)) if association_filter?(values)
|
|
84
|
+
result
|
|
85
|
+
end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
86
|
+
|
|
87
|
+
def association_filter?(values)
|
|
88
|
+
!association_steps(values).empty? && !filter_widget(values).nil?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def association_filter(values) # rubocop:disable Metrics/AbcSize
|
|
92
|
+
filter = filter_widget(values)
|
|
93
|
+
steps = association_steps(values)
|
|
94
|
+
steps.flat_map { [_1['Association'], _1['DestinationEntity']] }.join('/')
|
|
95
|
+
association = steps.first['Association'].to_s
|
|
96
|
+
endpoint = steps.last['DestinationEntity'].to_s
|
|
97
|
+
caption = attribute_name(values).split('.').last
|
|
98
|
+
selectable_id = "#{data_source_id}$#{filter['Name']}"
|
|
99
|
+
operation_id = WebOperationCompiler.operation_id(
|
|
100
|
+
@page_name, "#{@widget['Name']}$#{filter['Name']}"
|
|
101
|
+
)
|
|
102
|
+
{
|
|
103
|
+
filterAssociation: raw_property('ListAssociationProperty', {
|
|
104
|
+
type: filter_boolean(filter, 'multiSelect') ? 'ReferenceSet' : 'Reference',
|
|
105
|
+
entity: :undefined, path: '', attribute: association, endpointEntity: endpoint,
|
|
106
|
+
selectableObjectsId: selectable_id, filterable: true, dataSourceId: data_source_id
|
|
107
|
+
}),
|
|
108
|
+
filterAssociationOptions: raw_property('DatabaseObjectListProperty', {
|
|
109
|
+
dataSourceId: selectable_id, entity: endpoint, operationId: operation_id,
|
|
110
|
+
sort: [[caption, 'asc']]
|
|
111
|
+
}),
|
|
112
|
+
filterAssociationOptionLabel: raw_property('ListExpressionProperty', {
|
|
113
|
+
expression: {
|
|
114
|
+
expr: { type: 'variable', variable: 'currentObject', path: caption },
|
|
115
|
+
args: { currentObject: { widget: widget_key, source: 'object' } }
|
|
116
|
+
}, dataSourceId: selectable_id
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def raw_property(name, config) = { '$raw' => "#{name}(#{js_object(config)})" }
|
|
122
|
+
|
|
123
|
+
def filter_widget(values)
|
|
124
|
+
array(values['filter']&.last&.fetch('Widgets', nil)).first
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def association_steps(values)
|
|
128
|
+
array(values['attribute']&.last&.dig('AttributeRef', 'EntityRef', 'Steps'))
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def filter_boolean(widget, key)
|
|
132
|
+
property_values(widget['Object'])[key]&.last&.fetch('PrimitiveValue', nil) == 'true'
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def dynamic_text(values, entity, name)
|
|
136
|
+
template = values['dynamicText']&.last&.fetch('TextTemplate', nil)
|
|
137
|
+
parameters = array(template&.fetch('Parameters', nil))
|
|
138
|
+
return :undefined unless parameters.one? && !name.empty?
|
|
139
|
+
|
|
140
|
+
variable = { type: 'variable', variable: 'currentObject', path: name }
|
|
141
|
+
expr = if attribute_type(entity, name) == 'DateTime'
|
|
142
|
+
{ type: 'function', name: '_format',
|
|
143
|
+
parameters: [variable, { type: 'literal', value: JSON.generate(type: 'datetime') }] }
|
|
144
|
+
else
|
|
145
|
+
variable
|
|
146
|
+
end
|
|
147
|
+
raw_list_expression(expr)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def raw_list_expression(expr)
|
|
151
|
+
payload = {
|
|
152
|
+
expression: { expr:, args: { currentObject: { widget: widget_key, source: 'object' } } },
|
|
153
|
+
dataSourceId: data_source_id
|
|
154
|
+
}
|
|
155
|
+
{ '$raw' => "ListExpressionProperty(#{js_object(payload)})" }
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def templated_content(values)
|
|
159
|
+
widgets = array(values['content']&.last&.fetch('Widgets', nil))
|
|
160
|
+
rendered = @render_widgets.call(widgets, widget_key, entity_name)
|
|
161
|
+
{ '$raw' => "TemplatedWidgetProperty(#{js_object(
|
|
162
|
+
dataSourceId: data_source_id, editable: false,
|
|
163
|
+
children: { '$raw' => "() => #{rendered}" }
|
|
164
|
+
)})" }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def compile_widgets(value)
|
|
168
|
+
widgets = array(value&.fetch('Widgets', nil))
|
|
169
|
+
return [] if widgets.empty?
|
|
170
|
+
|
|
171
|
+
{ '$raw' => @render_widgets.call(widgets, widget_key, entity_name) }
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def raw_attribute(entity, name)
|
|
175
|
+
{
|
|
176
|
+
'$raw' => "AttributeProperty(#{js_object(
|
|
177
|
+
path: '', entity:, attribute: name, attributeType: attribute_type(entity, name),
|
|
178
|
+
sortable: true, filterable: true, dataSourceId: data_source_id, isList: false
|
|
179
|
+
)})"
|
|
180
|
+
}
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def expression(value)
|
|
184
|
+
payload = { expression: { expr: { type: 'literal', value: }, args: {} } }
|
|
185
|
+
{ '$raw' => "ExpressionProperty(#{js_object(payload)})" }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def js_object(value)
|
|
189
|
+
pairs = value.map { |key, item| "#{JSON.generate(key)}: #{js_value(item)}" }
|
|
190
|
+
"{ #{pairs.join(', ')} }"
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def js_value(value)
|
|
194
|
+
return value['$raw'] if value.is_a?(Hash) && value.key?('$raw')
|
|
195
|
+
return 'undefined' if value == :undefined
|
|
196
|
+
return "[#{value.map { js_value(_1) }.join(', ')}]" if value.is_a?(Array)
|
|
197
|
+
return js_object(value) if value.is_a?(Hash)
|
|
198
|
+
|
|
199
|
+
JSON.generate(value)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def property_values(object)
|
|
203
|
+
array(object&.fetch('Properties', nil)).filter_map do |property|
|
|
204
|
+
type = @index[IO::BsonCodec.extract_id(property['TypePointer'])]
|
|
205
|
+
next unless type
|
|
206
|
+
|
|
207
|
+
[type.fetch('PropertyKey'), [type.dig('ValueType', 'Type'), property['Value']]]
|
|
208
|
+
end.to_h
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def columns
|
|
212
|
+
value = property_values(@widget['Object']).fetch('columns').last
|
|
213
|
+
array(value['Objects'])
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def supported_column?(object)
|
|
217
|
+
values = property_values(object)
|
|
218
|
+
mode = values['showContentAs']&.last&.fetch('PrimitiveValue', nil)
|
|
219
|
+
return attribute_name(values).include?('.') if %w[attribute dynamicText].include?(mode)
|
|
220
|
+
return false unless mode == 'customContent' && @render_widgets
|
|
221
|
+
|
|
222
|
+
array(values['content']&.last&.fetch('Widgets', nil)).any?
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def attribute_name(values) = values['attribute']&.last&.dig('AttributeRef', 'Attribute').to_s
|
|
226
|
+
|
|
227
|
+
def xpath_source
|
|
228
|
+
property_values(@widget['Object'])['datasource']&.last&.fetch('DataSource', nil)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def entity_name = xpath_source.dig('EntityRef', 'Entity').to_s
|
|
232
|
+
def data_source_id = "p.#{Digest::SHA256.hexdigest(widget_key)[0, 6].to_i(16)}"
|
|
233
|
+
def widget_key = "p.#{@page_name}.#{@widget['Name']}"
|
|
234
|
+
|
|
235
|
+
def css_class
|
|
236
|
+
["mx-name-#{@widget['Name']}", @widget.dig('Appearance', 'Class')]
|
|
237
|
+
.map(&:to_s).reject(&:empty?).uniq.join(' ')
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def translated_text(text)
|
|
241
|
+
items = array(text&.dig('Template', 'Items'))
|
|
242
|
+
items.find { _1['LanguageCode'] == 'en_US' }&.fetch('Text', '') ||
|
|
243
|
+
items.first&.fetch('Text', '') || ''
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def text_value(value) = translated_text(value&.fetch('TextTemplate', nil))
|
|
247
|
+
|
|
248
|
+
def attribute_type(entity, name) # rubocop:disable Metrics/AbcSize
|
|
249
|
+
document = @source.units_of('DomainModels$DomainModel').filter_map do |unit|
|
|
250
|
+
next unless unit.module_name == entity.split('.').first
|
|
251
|
+
|
|
252
|
+
array(unit.document['Entities']).find { _1['Name'] == entity.split('.').last }
|
|
253
|
+
end.first
|
|
254
|
+
attribute = array(document&.fetch('Attributes', nil)).find { _1['Name'] == name }
|
|
255
|
+
attribute_type_name(attribute&.fetch('NewType', nil))
|
|
256
|
+
end # rubocop:enable Metrics/AbcSize
|
|
257
|
+
|
|
258
|
+
def attribute_type_name(type)
|
|
259
|
+
type_name = type&.fetch('$Type', '').to_s.delete_prefix('DomainModels$')
|
|
260
|
+
type_name.delete_suffix('AttributeType').then { _1.empty? ? 'String' : _1 }
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def widget_type
|
|
264
|
+
object_type_id = IO::BsonCodec.extract_id(@widget.dig('Object', 'TypePointer'))
|
|
265
|
+
return unless object_type_id
|
|
266
|
+
|
|
267
|
+
@index.values.find do |document|
|
|
268
|
+
IO::BsonCodec.extract_id(document.dig('ObjectType', '$ID')) == object_type_id
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def document_index
|
|
273
|
+
return @source.document_index if @source.is_a?(SourceModel)
|
|
274
|
+
|
|
275
|
+
{}.tap { |index| @source.documents.each { index_document(_1, index) } }
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def index_document(value, index)
|
|
279
|
+
case value
|
|
280
|
+
when Hash
|
|
281
|
+
id = IO::BsonCodec.extract_id(value['$ID'])
|
|
282
|
+
index[id] = value if id
|
|
283
|
+
value.each_value { index_document(_1, index) }
|
|
284
|
+
when Array then value.each { index_document(_1, index) }
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
# rubocop:enable Metrics
|
|
289
|
+
end
|
|
290
|
+
end
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module Mxrb
|
|
7
|
+
module Compiler
|
|
8
|
+
# Mirrors Mendix's Database Connector build extension by lowering custom activities to Java calls.
|
|
9
|
+
# The mapping mirrors one cohesive external build-extension contract.
|
|
10
|
+
# rubocop:disable Metrics/AbcSize, Metrics/ClassLength, Metrics/CyclomaticComplexity,
|
|
11
|
+
# rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
12
|
+
class DatabaseConnectorActionCompiler
|
|
13
|
+
include ModelValues
|
|
14
|
+
|
|
15
|
+
def initialize(source)
|
|
16
|
+
@source = source
|
|
17
|
+
@connections = connection_index
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def compile(action)
|
|
21
|
+
connection, query = resolve_query(action['Query'].to_s)
|
|
22
|
+
java_action = java_action_for(query)
|
|
23
|
+
mappings = basic_mappings(action, connection, query, java_action)
|
|
24
|
+
mappings.concat(result_mappings(query, java_action))
|
|
25
|
+
{
|
|
26
|
+
'$ID' => action['$ID'], '$Type' => 'Microflows$JavaActionCallAction',
|
|
27
|
+
'QueueSettings' => nil, 'ParameterMappings' => mappings,
|
|
28
|
+
'ErrorHandlingType' => action['ErrorHandlingType'], 'JavaAction' => java_action,
|
|
29
|
+
'ResultVariableName' => action['OutputVariableName'].to_s,
|
|
30
|
+
'UseReturnVariable' => true
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def unconfigured_write?(action)
|
|
35
|
+
return false unless action&.fetch('$Type', nil) == 'DatabaseConnector$ExecuteDatabaseQueryAction'
|
|
36
|
+
|
|
37
|
+
connection, query = resolve_query(action['Query'].to_s)
|
|
38
|
+
return false if selecting_query?(query)
|
|
39
|
+
|
|
40
|
+
override = array(action['ConnectionParameterMappings']).find do |mapping|
|
|
41
|
+
mapping['ParameterName'].to_s.casecmp('DBSource').zero?
|
|
42
|
+
end
|
|
43
|
+
return false unless override.nil? || override['Value'].to_s.strip.empty?
|
|
44
|
+
|
|
45
|
+
constant_name = connection['ConnectionString'].to_s
|
|
46
|
+
constant = @source.units.find do |unit|
|
|
47
|
+
unit.document['$Type'] == 'Constants$Constant' &&
|
|
48
|
+
"#{unit.module_name}.#{unit.document['Name']}" == constant_name
|
|
49
|
+
end
|
|
50
|
+
constant && constant.document['DefaultValue'].to_s.empty?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def connection_index
|
|
56
|
+
@source.units.each_with_object({}) do |unit, result|
|
|
57
|
+
next unless unit.document['$Type'] == 'DatabaseConnector$DatabaseConnection'
|
|
58
|
+
|
|
59
|
+
result["#{unit.module_name}.#{unit.document['Name']}"] = unit.document
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def resolve_query(qualified)
|
|
64
|
+
connection_name, _, query_name = qualified.rpartition('.')
|
|
65
|
+
connection = @connections[connection_name]
|
|
66
|
+
raise CompilationError, "database connection #{connection_name.inspect} not found" unless connection
|
|
67
|
+
|
|
68
|
+
query = array(connection['Queries']).find { _1['Name'] == query_name }
|
|
69
|
+
raise CompilationError, "database query #{qualified.inspect} not found" unless query
|
|
70
|
+
|
|
71
|
+
[connection, query]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def java_action_for(query)
|
|
75
|
+
sql = remove_comments(query['Query'].to_s).downcase
|
|
76
|
+
selecting = selecting_sql?(sql)
|
|
77
|
+
mapped = array(query['TableMappings']).any?
|
|
78
|
+
if selecting
|
|
79
|
+
return mapped ? 'ExternalDatabaseConnector.ExecuteQuery' : 'ExternalDatabaseConnector.ExecuteStatement'
|
|
80
|
+
end
|
|
81
|
+
return statement_action(mapped) unless callable?(query, sql)
|
|
82
|
+
return 'ExternalDatabaseConnector.ExecuteCallable' if query['QueryType'].to_i == 3
|
|
83
|
+
|
|
84
|
+
if mapped
|
|
85
|
+
'ExternalDatabaseConnector.ExecuteCallableQuery'
|
|
86
|
+
else
|
|
87
|
+
'ExternalDatabaseConnector.ExecuteCallableStatement'
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def selecting_query?(query)
|
|
92
|
+
selecting_sql?(remove_comments(query['Query'].to_s).downcase)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def selecting_sql?(sql)
|
|
96
|
+
(sql.start_with?('select ') && !sql.include?(' into ')) || sql.start_with?('with ')
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def statement_action(mapped)
|
|
100
|
+
mapped ? 'ExternalDatabaseConnector.ExecuteQuery' : 'ExternalDatabaseConnector.ExecuteStatement'
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def callable?(query, sql)
|
|
104
|
+
return true if query['QueryType'].to_i == 3
|
|
105
|
+
return false unless query['QueryType'].to_i.zero?
|
|
106
|
+
|
|
107
|
+
sql.match?(/\A\s*(?:\{\s*)?(?:call|exec(?:ute)?)\b/i)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def basic_mappings(action, connection, query, java_action)
|
|
111
|
+
values = {
|
|
112
|
+
'connectionDetails' => expression_literal(connection_json(action, connection)),
|
|
113
|
+
'sql' => expression_literal(runtime_query(action, query)),
|
|
114
|
+
'queryParameters' => expression_literal(parameters_json(action, query))
|
|
115
|
+
}
|
|
116
|
+
values.map.with_index { |(name, value), index| parameter_mapping(action, java_action, name, value, index) }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def result_mappings(query, java_action)
|
|
120
|
+
return [] unless %w[
|
|
121
|
+
ExternalDatabaseConnector.ExecuteQuery ExternalDatabaseConnector.ExecuteCallableQuery
|
|
122
|
+
ExternalDatabaseConnector.ExecuteCallable
|
|
123
|
+
].include?(java_action)
|
|
124
|
+
|
|
125
|
+
table = array(query['TableMappings']).first
|
|
126
|
+
return [] unless table
|
|
127
|
+
|
|
128
|
+
column_mapping = JSON.generate(
|
|
129
|
+
'EntityName' => table['Entity'].to_s,
|
|
130
|
+
'ColumnAttributeMapping' => array(table['Columns']).to_h do |column|
|
|
131
|
+
[column['ColumnName'].to_s, column['Attribute'].to_s.split('.').last]
|
|
132
|
+
end
|
|
133
|
+
)
|
|
134
|
+
offset = 10
|
|
135
|
+
[
|
|
136
|
+
parameter_mapping(query, java_action, 'columnMapping', expression_literal(column_mapping), offset),
|
|
137
|
+
entity_parameter_mapping(query, java_action, 'OutputEntity', table['Entity'].to_s, offset + 1)
|
|
138
|
+
]
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def connection_json(action, connection)
|
|
142
|
+
overrides = array(action['ConnectionParameterMappings']).to_h do |mapping|
|
|
143
|
+
[mapping['ParameterName'].to_s.downcase, mapping['Value'].to_s]
|
|
144
|
+
end
|
|
145
|
+
values = {
|
|
146
|
+
'UserName' => raw(connection_value(overrides, connection, 'DBUsername', 'UserName')),
|
|
147
|
+
'Password' => raw(connection_value(overrides, connection, 'DBPassword', 'Password')),
|
|
148
|
+
'ConnectionString' => raw(connection_value(overrides, connection, 'DBSource', 'ConnectionString')),
|
|
149
|
+
'DatabaseType' => connection['DatabaseType'].to_s,
|
|
150
|
+
'AdditionalProperties' => additional_properties(overrides, connection)
|
|
151
|
+
}
|
|
152
|
+
raw_json(values)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def connection_value(overrides, connection, parameter, field)
|
|
156
|
+
expression = overrides[parameter.downcase]
|
|
157
|
+
expression ||= "@#{connection[field]}" unless connection[field].to_s.empty?
|
|
158
|
+
expression ||= "''"
|
|
159
|
+
"'+(#{expression})+'"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def additional_properties(overrides, connection)
|
|
163
|
+
array(connection['AdditionalProperties']).to_h do |property|
|
|
164
|
+
value = property['Value'] || {}
|
|
165
|
+
expression = overrides[property['Key'].to_s.downcase]
|
|
166
|
+
rendered = if expression
|
|
167
|
+
raw("'+(#{expression})+'")
|
|
168
|
+
elsif value['$Type'] == 'DatabaseConnector$ValueAsConstant'
|
|
169
|
+
raw("'+(@#{value['Value']})+'")
|
|
170
|
+
else
|
|
171
|
+
value['Value'].to_s
|
|
172
|
+
end
|
|
173
|
+
[property['Key'].to_s, rendered]
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def runtime_query(action, query)
|
|
178
|
+
dynamic = action['DynamicQuery'].to_s
|
|
179
|
+
return "'+(#{dynamic})+'" unless dynamic.strip.empty?
|
|
180
|
+
|
|
181
|
+
sql = query['Query'].to_s
|
|
182
|
+
sql = query_builder_select(query) if sql.strip.empty?
|
|
183
|
+
sql.gsub("'", "''")
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def query_builder_select(query)
|
|
187
|
+
table = array(query['TableMappings']).first
|
|
188
|
+
columns = table && array(table['Columns']).filter_map { valid_identifier(_1['ColumnName']) }
|
|
189
|
+
name = table && valid_identifier(table['TableName'])
|
|
190
|
+
unless name && columns.any?
|
|
191
|
+
raise CompilationError, 'database query builder has no executable table and column mapping'
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
"SELECT #{columns.join(', ')} FROM #{name}"
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def valid_identifier(value)
|
|
198
|
+
identifier = value.to_s
|
|
199
|
+
return identifier if identifier.match?(/\A[A-Za-z_][A-Za-z0-9_$]*(?:\.[A-Za-z_][A-Za-z0-9_$]*)*\z/)
|
|
200
|
+
|
|
201
|
+
raise CompilationError, "unsafe database identifier #{identifier.inspect}"
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def parameters_json(action, query)
|
|
205
|
+
mappings = array(action['ParameterMappings']).to_h do |mapping|
|
|
206
|
+
[mapping['ParameterName'].to_s.downcase, mapping['Value'].to_s]
|
|
207
|
+
end
|
|
208
|
+
result = {}
|
|
209
|
+
array(query['Parameters']).each_with_index do |parameter, index|
|
|
210
|
+
type = parameter.dig('DataType', '$Type').to_s.sub(/\ADataTypes\$/, '').sub(/Type\z/, '').upcase
|
|
211
|
+
value = parameter_value(type, mappings[parameter['ParameterName'].to_s.downcase])
|
|
212
|
+
result[(index + 1).to_s] = {
|
|
213
|
+
'Name' => parameter['ParameterName'], 'DataType' => type,
|
|
214
|
+
'SqlDataType' => parameter.dig('SqlDataType', 'DataTypeName').to_s,
|
|
215
|
+
'Value' => value && raw(value), 'ParameterMode' => parameter['Mode'].to_s,
|
|
216
|
+
'DatabaseParameterName' => parameter['DatabaseParameterName'].to_s,
|
|
217
|
+
'ParameterEntityMapping' => nil
|
|
218
|
+
}
|
|
219
|
+
end
|
|
220
|
+
raw_json(result)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def parameter_value(type, expression)
|
|
224
|
+
case type
|
|
225
|
+
when 'DATETIME' then "'+(if (#{expression}) = NULL then NULL else dateTimeToEpoch(#{expression}))+'"
|
|
226
|
+
when 'BOOLEAN' then "'+toString(#{expression})+'"
|
|
227
|
+
when 'STRING'
|
|
228
|
+
"'+(if (#{expression}) = NULL then NULL else (if (#{expression}) = 'null' then " \
|
|
229
|
+
"'903be4ca-ed8b-ddcb-a339-741c4088484a' else (urlEncode(#{expression}))))+'"
|
|
230
|
+
when 'INTEGER', 'DECIMAL' then "'+(#{expression})+'"
|
|
231
|
+
else raise CompilationError, "unsupported database parameter type #{type.inspect}"
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def expression_literal(value) = "'#{value}'"
|
|
236
|
+
|
|
237
|
+
def parameter_mapping(seed, java_action, name, value, index)
|
|
238
|
+
{
|
|
239
|
+
'$ID' => derived_id(seed, "mapping-#{index}"), '$Type' => 'Microflows$JavaActionParameterMapping',
|
|
240
|
+
'Parameter' => "#{java_action}.#{name}",
|
|
241
|
+
'Value' => {
|
|
242
|
+
'$ID' => derived_id(seed, "value-#{index}"),
|
|
243
|
+
'$Type' => 'Microflows$BasicCodeActionParameterValue', 'ValueExpression' => value
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def entity_parameter_mapping(seed, java_action, name, entity, index)
|
|
249
|
+
{
|
|
250
|
+
'$ID' => derived_id(seed, "mapping-#{index}"), '$Type' => 'Microflows$JavaActionParameterMapping',
|
|
251
|
+
'Parameter' => "#{java_action}.#{name}",
|
|
252
|
+
'Value' => {
|
|
253
|
+
'$ID' => derived_id(seed, "value-#{index}"),
|
|
254
|
+
'$Type' => 'Microflows$EntityTypeCodeActionParameterValue',
|
|
255
|
+
'Entity' => entity, 'ValueExpression' => "'#{entity}'"
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def raw(value) = { '__mxrb_raw__' => value }
|
|
261
|
+
|
|
262
|
+
def raw_json(value)
|
|
263
|
+
rendered = JSON.generate(value)
|
|
264
|
+
rendered.gsub(/\{"__mxrb_raw__":"((?:\\.|[^"\\])*)"\}/) do
|
|
265
|
+
JSON.parse(%("#{Regexp.last_match(1)}"))
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def remove_comments(sql) = sql.gsub(%r{/\*[\s\S]*?\*/|--.*}, '').strip
|
|
270
|
+
|
|
271
|
+
def derived_id(source, label)
|
|
272
|
+
seed = "#{IO::BsonCodec.extract_id(source['$ID'])}:database-connector:#{label}"
|
|
273
|
+
hex = Digest::SHA256.hexdigest(seed)[0, 32]
|
|
274
|
+
"#{hex[0, 8]}-#{hex[8, 4]}-4#{hex[13, 3]}-8#{hex[17, 3]}-#{hex[20, 12]}"
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
# rubocop:enable Metrics/AbcSize, Metrics/ClassLength, Metrics/CyclomaticComplexity,
|
|
278
|
+
# rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
279
|
+
end
|
|
280
|
+
end
|