Skip to content

Commit

Permalink
feat: add query type support for SOQL
Browse files Browse the repository at this point in the history
  • Loading branch information
afthabvp authored Apr 9, 2024
1 parent 4b087f1 commit c5a2055
Show file tree
Hide file tree
Showing 17 changed files with 270 additions and 60 deletions.
2 changes: 1 addition & 1 deletion server/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ gem "interactor", "~> 3.0"

gem "ruby-odbc", git: "https://github.com/Multiwoven/ruby-odbc.git"

gem "multiwoven-integrations", "~> 0.1.52"
gem "multiwoven-integrations", "~> 0.1.55"

gem "temporal-ruby", github: "coinbase/temporal-ruby"

Expand Down
26 changes: 13 additions & 13 deletions server/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,12 @@ GEM
fiber-annotation
io-event (~> 1.5, >= 1.5.1)
timers (~> 4.1)
async-http (0.64.0)
async-http (0.64.1)
async (>= 1.25)
async-io (>= 1.28)
async-pool (>= 0.2)
protocol-http (~> 0.26.0)
protocol-http1 (~> 0.18.0)
protocol-http1 (~> 0.19.0)
protocol-http2 (~> 0.16.0)
traces (>= 0.10.0)
async-io (1.42.0)
Expand Down Expand Up @@ -1755,7 +1755,7 @@ GEM
gli (2.21.1)
globalid (1.2.1)
activesupport (>= 6.1)
google-apis-bigquery_v2 (0.66.0)
google-apis-bigquery_v2 (0.67.0)
google-apis-core (>= 0.14.0, < 2.a)
google-apis-core (0.14.1)
addressable (~> 2.5, >= 2.5.1)
Expand Down Expand Up @@ -1859,7 +1859,7 @@ GEM
msgpack (1.7.2)
multi_json (1.15.0)
multipart-post (2.4.0)
multiwoven-integrations (0.1.52)
multiwoven-integrations (0.1.55)
activesupport
async-websocket
csv
Expand Down Expand Up @@ -1893,7 +1893,7 @@ GEM
net-ssh (>= 5.0.0, < 8.0.0)
net-smtp (0.4.0.1)
net-protocol
net-ssh (7.2.1)
net-ssh (7.2.3)
newrelic_rpm (9.7.0)
nio4r (2.7.0)
nokogiri (1.16.0-aarch64-linux)
Expand All @@ -1914,20 +1914,20 @@ GEM
pg_query (5.0.0)
google-protobuf (>= 3.22.3)
protocol-hpack (1.4.3)
protocol-http (0.26.1)
protocol-http1 (0.18.0)
protocol-http (0.26.2)
protocol-http1 (0.19.0)
protocol-http (~> 0.22)
protocol-http2 (0.16.0)
protocol-hpack (~> 1.4)
protocol-http (~> 0.18)
protocol-rack (0.4.1)
protocol-rack (0.5.0)
protocol-http (~> 0.23)
rack (>= 1.0)
protocol-websocket (0.12.1)
protocol-http (~> 0.2)
psych (5.1.2)
stringio
public_suffix (5.0.4)
public_suffix (5.0.5)
puma (6.4.2)
nio4r (~> 2.0)
racc (1.7.3)
Expand Down Expand Up @@ -1986,7 +1986,7 @@ GEM
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
restforce (7.3.0)
restforce (7.3.1)
faraday (>= 1.1.0, < 2.10.0)
faraday-follow_redirects (<= 0.3.0, < 1.0.0)
faraday-multipart (>= 1.0.0, < 2.0.0)
Expand Down Expand Up @@ -2036,7 +2036,7 @@ GEM
ruby-limiter (2.3.0)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
sequel (5.78.0)
sequel (5.79.0)
bigdecimal
shoulda-matchers (5.3.0)
activesupport (>= 5.2.0)
Expand All @@ -2060,7 +2060,7 @@ GEM
gli
hashie
stringio (3.1.0)
stripe (10.13.0)
stripe (10.14.0)
thor (1.3.0)
timecop (0.9.8)
timeout (0.4.1)
Expand Down Expand Up @@ -2119,7 +2119,7 @@ DEPENDENCIES
jwt
kaminari
liquid
multiwoven-integrations (~> 0.1.52)
multiwoven-integrations (~> 0.1.55)
newrelic_rpm
parallel
pg (~> 1.1)
Expand Down
6 changes: 0 additions & 6 deletions server/app/contracts/connector_contracts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,5 @@ class QuerySource < Dry::Validation::Contract
required(:id).filled(:integer)
required(:query).filled(:string)
end
# TODO: introduce query_type SOQL
rule(:query) do
# PgQuery.parse(value)
rescue PgQuery::ParseError => e
key.failure("contains invalid SQL syntax: #{e.message}")
end
end
end
16 changes: 0 additions & 16 deletions server/app/contracts/model_contracts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ class Create < Dry::Validation::Contract
end
end

rule(model: :query) do
# PgQuery.parse(value)
rescue PgQuery::ParseError
key.failure("contains invalid SQL syntax")
end

rule(model: :query_type) do
key.failure("invalid query type") unless Multiwoven::Integrations::Protocol::ModelQueryType.include?(value)
end
Expand All @@ -47,16 +41,6 @@ class Update < Dry::Validation::Contract
end
end

rule(model: :query) do
if key?
begin
# PgQuery.parse(value)
rescue PgQuery::ParseError => e
key.failure("contains invalid SQL syntax: #{e.message}")
end
end
end

rule(model: :query_type) do
unless !key? || Multiwoven::Integrations::Protocol::ModelQueryType.include?(value)
key.failure("invalid query type")
Expand Down
12 changes: 11 additions & 1 deletion server/app/controllers/api/v1/connectors_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module V1
class ConnectorsController < ApplicationController
include Connectors
before_action :set_connector, only: %i[show update destroy discover query_source]
before_action :validate_query, only: %i[query_source]
after_action :event_logger

def index
Expand Down Expand Up @@ -112,10 +113,19 @@ def set_connector
)
end

def validate_query
Utils::QueryValidator.validate_query(@connector.connector_query_type, params[:query])
rescue StandardError => e
render_error(
message: "Query validation failed: #{e.message}",
status: :unprocessable_entity
)
end

def connector_params
params.require(:connector).permit(:workspace_id,
:connector_type,
:connector_name, :name, :description,
:connector_name, :name, :description, :query_type,
configuration: {})
end
end
Expand Down
11 changes: 11 additions & 0 deletions server/app/controllers/api/v1/models_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class ModelsController < ApplicationController

before_action :set_connector, only: %i[create]
before_action :set_model, only: %i[show update destroy]
before_action :validate_query, only: %i[create update]
after_action :event_logger

def index
Expand Down Expand Up @@ -70,6 +71,16 @@ def set_model
@model = current_workspace.models.find(params[:id])
end

def validate_query
query_type = @model.present? ? @model.connector.connector_query_type : @connector.connector_query_type
Utils::QueryValidator.validate_query(query_type, params.dig(:model, :query))
rescue StandardError => e
render_error(
message: "Query validation failed: #{e.message}",
status: :unprocessable_entity
)
end

def model_params
params.require(:model).permit(:connector_id,
:name,
Expand Down
10 changes: 10 additions & 0 deletions server/app/models/connector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,14 @@ def connector_client
connector_type.to_s.camelize, connector_name.to_s.camelize
)
end

def connector_query_type
client = Multiwoven::Integrations::Service
.connector_class(
connector_type.to_s.camelize, connector_name.to_s.camelize
).new
connector_spec = client.connector_spec

connector_spec&.connector_query_type || "raw_sql"
end
end
2 changes: 1 addition & 1 deletion server/app/models/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Model < ApplicationRecord
validates :name, presence: true
validates :query, presence: true
validates :primary_key, presence: true
enum :query_type, %i[raw_sql]
enum :query_type, %i[raw_sql dbt soql]

belongs_to :workspace
belongs_to :connector
Expand Down
3 changes: 2 additions & 1 deletion server/db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions server/lib/utils/query_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

module Utils
module QueryValidator
def self.validate_query(query_type, query)
case query_type.to_sym
when :raw_sql
begin
PgQuery.parse(query)
rescue PgQuery::ParseError => e
raise StandardError, "Query contains invalid SQL syntax: #{e.message}"
end
when :soql
begin
# TODO: SOQL
rescue StandardError => e
raise StandardError, "Query contains invalid SOQL syntax: #{e.message}"
end
else
raise StandardError, "Unsupported query_type"
end
end
end
end
9 changes: 0 additions & 9 deletions server/spec/contracts/connector_contracts_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,6 @@
expect(contract.call(valid_inputs)).to be_success
end
end

context "when query contains invalid SQL syntax" do
let(:invalid_inputs) { { id: 1, query: "SELECT FROM table;" } }

it "fails validation" do
# result = contract.call(invalid_inputs)
# expect(result.errors[:query]).to include(a_string_matching("contains invalid SQL syntax"))
end
end
end

describe ConnectorContracts::Discover do
Expand Down
36 changes: 24 additions & 12 deletions server/spec/contracts/model_contracts_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,28 +62,22 @@
end
end

context "with invalid SQL syntax in query" do
context "with invalid query_type" do
let(:invalid_inputs) do
{
model: {
connector_id: 1,
name: "Model Name",
query: "SELECT FROM table;",
query_type: "raw_sql",
query_type: "test",
primary_key: "id"
}
}
end

it "fails validation due to invalid SQL syntax" do
allow(PgQuery).to receive(:parse).with("SELECT FROM table;").and_raise(
PgQuery::ParseError.new(
"invalid syntax",
__FILE__, __LINE__, -1
)
)
# result = contract.call(invalid_inputs)
# expect(result.errors[:model][:query]).to include("contains invalid SQL syntax")
result = contract.call(invalid_inputs)
expect(result.errors[:model][:query_type]).to include("invalid query type")
end
end
end
Expand All @@ -98,17 +92,35 @@
model: {
name: "Updated Model Name",
query: "SELECT * FROM updated_table;",
query_type: "raw_sql",
query_type: "soql",
primary_key: "updated_id"
}
}
end

it "passes validation" do
allow(PgQuery).to receive(:parse).with("SELECT * FROM updated_table;").and_return(true)
expect(contract.call(valid_inputs)).to be_success
end
end

context "with invalid query_type" do
let(:invalid_inputs) do
{
model: {
connector_id: 1,
name: "Model Name",
query: "SELECT FROM table;",
query_type: "test",
primary_key: "id"
}
}
end

it "fails validation due to invalid SQL syntax" do
result = contract.call(invalid_inputs)
expect(result.errors[:model][:query_type]).to include("invalid query type")
end
end
end

describe ModelContracts::Destroy do
Expand Down
38 changes: 38 additions & 0 deletions server/spec/lib/utils/query_validator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe Utils::QueryValidator do
describe ".validate_query" do
context "with valid SQL query" do
let(:query_type) { :raw_sql }
let(:query) { "SELECT * FROM users;" }

it "does not raise an error" do
expect { described_class.validate_query(query_type, query) }.not_to raise_error
end
end

context "with invalid SQL query" do
let(:query_type) { :raw_sql }
let(:query) { "INVALID SQL QUERY;" }

it "raises a StandardError with error message" do
expect do
described_class.validate_query(query_type, query)
end.to raise_error(StandardError, /contains invalid SQL syntax/)
end
end

context "with unsupported query type" do
let(:query_type) { :unsupported }
let(:query) { "SELECT * FROM users;" }

it "raises a StandardError with error message" do
expect do
described_class.validate_query(query_type, query)
end.to raise_error(StandardError, /Unsupported query_type/)
end
end
end
end
Loading

0 comments on commit c5a2055

Please sign in to comment.