Skip to content

Commit

Permalink
add SHOW tables; custom keyword support; intro KeywordDefinition
Browse files Browse the repository at this point in the history
  • Loading branch information
David Coe committed Jan 12, 2024
1 parent e29804b commit 84e3ecc
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 109 deletions.
5 changes: 2 additions & 3 deletions csharp/src/Client/AdbcCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public sealed class AdbcCommand : DbCommand
{
private AdbcStatement _adbcStatement;
private int _timeout = 30;
public QueryConfiguration _queryConfiguration;
public QueryConfiguration _queryConfiguration = new QueryConfiguration();

/// <summary>
/// Overloaded. Initializes <see cref="AdbcCommand"/>.
Expand All @@ -54,7 +54,6 @@ public AdbcCommand(AdbcStatement adbcStatement, AdbcConnection adbcConnection) :
this._adbcStatement = adbcStatement;
this.DbConnection = adbcConnection;
this.DecimalBehavior = adbcConnection.DecimalBehavior;
this._queryConfiguration = new QueryConfiguration();
}

/// <summary>
Expand Down Expand Up @@ -196,7 +195,7 @@ protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)

foreach(Query q in queries)
{
if (q.Type == QueryType.Read)
if (q.Type == QueryReturnType.RecordSet)
{
if(queryResult == null)
{
Expand Down
146 changes: 73 additions & 73 deletions csharp/src/Client/QueryParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Apache.Arrow.Adbc.Client
Expand All @@ -28,93 +27,101 @@ namespace Apache.Arrow.Adbc.Client
/// </summary>
public class QueryConfiguration
{
private string[] _keywords = new string[] { };

public QueryConfiguration()
{
CreateKeyword = "CREATE";
SelectKeyword = "SELECT";
UpdateKeyword = "UPDATE";
DeleteKeyword = "DELETE";
DropKeyword = "DROP";
InsertKeyword = "INSERT";

this.AdditionalKeywords = new string[] { };
CreateKeyword = new KeywordDefinition("CREATE", QueryReturnType.RecordsAffected);
SelectKeyword = new KeywordDefinition("SELECT", QueryReturnType.RecordSet);
UpdateKeyword = new KeywordDefinition("UPDATE", QueryReturnType.RecordsAffected);
DeleteKeyword = new KeywordDefinition("DELETE", QueryReturnType.RecordsAffected);
DropKeyword = new KeywordDefinition("DROP", QueryReturnType.RecordsAffected);
InsertKeyword = new KeywordDefinition("INSERT", QueryReturnType.RecordsAffected);

Keywords = new Dictionary<string,QueryReturnType>(StringComparer.OrdinalIgnoreCase)
{
{ CreateKeyword.Keyword, CreateKeyword.ReturnType },
{ SelectKeyword.Keyword, SelectKeyword.ReturnType },
{ UpdateKeyword.Keyword, UpdateKeyword.ReturnType },
{ DeleteKeyword.Keyword, DeleteKeyword.ReturnType },
{ DropKeyword.Keyword, DropKeyword.ReturnType },
{ InsertKeyword.Keyword, InsertKeyword.ReturnType },
};
}

/// <summary>
/// The CREATE keyword. CREATE by default.
/// </summary>
public string CreateKeyword { get; set; }
public KeywordDefinition CreateKeyword { get; set; }

/// <summary>
/// The SELECT keyword. SELECT by default.
/// </summary>
public string SelectKeyword { get; set; }
public KeywordDefinition SelectKeyword { get; set; }

/// <summary>
/// The UPDATE keyword. UPDATE by default.
/// </summary>
public string UpdateKeyword { get; set; }
public KeywordDefinition UpdateKeyword { get; set; }

/// <summary>
/// The INSERT keyword. INSERT by default.
/// </summary>
public string InsertKeyword { get; set; }
public KeywordDefinition InsertKeyword { get; set; }

/// <summary>
/// The DELETE keyword. DELETE by default.
/// </summary>
public string DeleteKeyword { get; set; }
public KeywordDefinition DeleteKeyword { get; set; }

/// <summary>
/// The DROP keyword. DROP by default.
/// </summary>
public string DropKeyword { get; set; }
public KeywordDefinition DropKeyword { get; set; }

/// <summary>
/// Optional additional keywords.
/// Keywords to parse from the query. Contains CREATE, SELECT, UPDATE, INSERT, DELETE, DROP by default.
/// </summary>
public string[] AdditionalKeywords { get; set; }
public Dictionary<string, QueryReturnType> Keywords { get; set; }

/// <summary>
/// All of the keywords that have been passed.
/// Optional. The caller can specify their own parsing function
/// to parse the queries instead of using the default one.
/// </summary>
public string[] AllKeywords
{
get
{
if(_keywords.Length > 0)
{
return _keywords;
}
public QueryFunctionDefinition CustomParser { get; set; }
}

List<string> keywords = new List<string>()
{
CreateKeyword,
SelectKeyword,
UpdateKeyword,
InsertKeyword,
DeleteKeyword,
DropKeyword
};

foreach(string kw in AdditionalKeywords)
{
keywords.Add(kw);
}
/// <summary>
/// A keyword definition.
/// </summary>
public class KeywordDefinition
{
/// <summary>
/// Overloaded. Initializes a <see cref="KeywordDefinition"/>.
/// </summary>
public KeywordDefinition()
{

_keywords = keywords.ToArray();
}

return _keywords;
}
/// <summary>
/// Overloaded. Initializes a <see cref="KeywordDefinition"/>.
/// </summary>
/// <param name="keyword">The keyword.</param>
/// <param name="queryReturnType">The expected return type.</param>
public KeywordDefinition(string keyword, QueryReturnType queryReturnType)
{
this.Keyword = keyword;
this.ReturnType = queryReturnType;
}

/// <summary>
/// Optional. The caller can specify their own parsing function
/// to parse the queries instead of using the default one.
/// The keyword.
/// </summary>
public QueryFunctionDefinition CustomParser { get; set; }
public string Keyword { get; set; }

/// <summary>
/// The expected return type.
/// </summary>
public QueryReturnType ReturnType { get; set; }
}

/// <summary>
Expand Down Expand Up @@ -162,7 +169,7 @@ internal List<Query> ParseQuery(string commandText)
}
else
{
userQueries = SplitStringWithKeywords(commandText, _queryConfiguration.AllKeywords);
userQueries = SplitStringWithKeywords(commandText);
}

return ParseQueries(userQueries);
Expand All @@ -180,29 +187,26 @@ private List<Query> ParseQueries(string[] userQueries)
Query query = new Query();
query.Text = userQuery.Trim();

if (query.Text.ToUpper().StartsWith(_queryConfiguration.CreateKeyword))
query.Type = QueryType.Create;
else if (query.Text.ToUpper().StartsWith(_queryConfiguration.SelectKeyword))
query.Type = QueryType.Read;
else if (query.Text.ToUpper().StartsWith(_queryConfiguration.InsertKeyword))
query.Type = QueryType.Insert;
else if (query.Text.ToUpper().StartsWith(_queryConfiguration.UpdateKeyword))
query.Type = QueryType.Update;
else if (query.Text.ToUpper().StartsWith(_queryConfiguration.DeleteKeyword))
query.Type = QueryType.Delete;
else if (query.Text.ToUpper().StartsWith(_queryConfiguration.DropKeyword))
query.Type = QueryType.Drop;
else
throw new InvalidOperationException("unable to parse query");
string firstWord = query.Text.Split(' ')[0];

queries.Add(query);
if(this._queryConfiguration.Keywords.TryGetValue(firstWord, out QueryReturnType returnType))
{
query.Type = returnType;
queries.Add(query);
}
else
{
throw new InvalidOperationException($"{firstWord} is not defined as a keyword");
}
}

return queries;
}

private string[] SplitStringWithKeywords(string input, string[] keywords)
private string[] SplitStringWithKeywords(string input)
{
string[] keywords = _queryConfiguration.Keywords.Keys.ToArray();

// Construct the regex pattern with capturing groups for keywords
string pattern = $"({string.Join("|", keywords.Select(Regex.Escape))})";

Expand Down Expand Up @@ -230,14 +234,10 @@ private string[] SplitStringWithKeywords(string input, string[] keywords)
/// <summary>
/// Specifies the type of query this is
/// </summary>
public enum QueryType
public enum QueryReturnType
{
Create,
Read,
Insert,
Update,
Delete,
Drop
RecordSet,
RecordsAffected
}

/// <summary>
Expand All @@ -253,6 +253,6 @@ internal class Query
/// <summary>
/// The query type
/// </summary>
public QueryType Type { get; set; }
public QueryReturnType Type { get; set; }
}
}
42 changes: 31 additions & 11 deletions csharp/test/Apache.Arrow.Adbc.Tests/Client/QueryParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,50 +38,55 @@ public class QueryParserTests
"DROP TABLE IF EXISTS TESTTABLE;CREATE TABLE TESTTABLE(intcol INT);INSERT INTO TESTTABLE VALUES (123);INSERT INTO TESTTABLE VALUES (456);SELECT * FROM TESTTABLE WHERE INTCOL=123;SELECT * FROM TESTTABLE WHERE INTCOL=456;SELECT * FROM TESTTABLE WHERE INTCOL=456;DROP TABLE TESTTABLE;",
"SELECT * FROM TESTTABLE WHERE INTCOL=123;",
8,
QueryType.Drop,QueryType.Create, QueryType.Insert, QueryType.Insert,QueryType.Read, QueryType.Read,QueryType.Read, QueryType.Drop
QueryReturnType.RecordsAffected,QueryReturnType.RecordsAffected, QueryReturnType.RecordsAffected, QueryReturnType.RecordsAffected, QueryReturnType.RecordSet, QueryReturnType.RecordSet,QueryReturnType.RecordSet, QueryReturnType.RecordsAffected
)]
[InlineData(
"DROP TABLE IF EXISTS TESTTABLE;CREATE TABLE TESTTABLE(intcol INT);INSERT INTO TESTTABLE VALUES (123);INSERT INTO TESTTABLE VALUES (456);SELECT * FROM TESTTABLE WHERE INTCOL=123;SELECT * FROM TESTTABLE WHERE INTCOL='item21;item2;item3';SELECT * FROM TESTTABLE WHERE INTCOL=456;DROP TABLE TESTTABLE;",
"SELECT * FROM TESTTABLE WHERE INTCOL=123;",
8,
QueryType.Drop, QueryType.Create, QueryType.Insert, QueryType.Insert, QueryType.Read, QueryType.Read, QueryType.Read, QueryType.Drop
QueryReturnType.RecordsAffected, QueryReturnType.RecordsAffected, QueryReturnType.RecordsAffected, QueryReturnType.RecordsAffected, QueryReturnType.RecordSet, QueryReturnType.RecordSet, QueryReturnType.RecordSet, QueryReturnType.RecordsAffected
)]
[InlineData(
"drop table if exists testtable;create table testtable(intcol int);insert into testtable values (123);insert into testtable values (456);select * from testtable where intcol=123;select * from testtable where intcol='item21;item2;item3';select * from testtable where intcol=456;drop table testtable;",
"select * from testtable where intcol=123;",
8,
QueryType.Drop, QueryType.Create, QueryType.Insert, QueryType.Insert, QueryType.Read, QueryType.Read, QueryType.Read, QueryType.Drop
QueryReturnType.RecordsAffected, QueryReturnType.RecordsAffected, QueryReturnType.RecordsAffected, QueryReturnType.RecordsAffected, QueryReturnType.RecordSet, QueryReturnType.RecordSet, QueryReturnType.RecordSet, QueryReturnType.RecordsAffected
)]
[InlineData(
"CREATE OR REPLACE TRANSIENT TABLE TESTTABLE(intcol INT);INSERT INTO TESTTABLE VALUES (123);INSERT INTO TESTTABLE VALUES (456);SELECT * FROM TESTTABLE WHERE INTCOL=123;",
"SELECT * FROM TESTTABLE WHERE INTCOL=123;",
4,
QueryType.Create, QueryType.Insert, QueryType.Insert, QueryType.Read
QueryReturnType.RecordsAffected, QueryReturnType.RecordsAffected, QueryReturnType.RecordsAffected, QueryReturnType.RecordSet
)]
[InlineData(
"select * from testtable where intcol=123",
"select * from testtable where intcol=123",
1,
QueryType.Read
QueryReturnType.RecordSet
)]
[InlineData(
"DELETE testtable where intcol=123",
"",
1,
QueryType.Delete
QueryReturnType.RecordsAffected
)]
public void ParseQuery(string query, string firstSelectQuery, int expectedQueries, params QueryType[] queryTypes)
public void ParseQuery(string query, string firstSelectQuery, int expectedQueries, params QueryReturnType[] queryTypes)
{
// uses the defaults
QueryConfiguration qc = new QueryConfiguration();

AssertValues(qc, query, firstSelectQuery, expectedQueries, queryTypes);

// do the same with a custom parser

QueryConfiguration customParsingQc = new QueryConfiguration();
List<string> keywords = new List<string>();
foreach (string key in customParsingQc.Keywords.Keys)
keywords.Add(key);

// do the same with a custom parser
customParsingQc.CustomParser = new QueryFunctionDefinition()
{
Parameter2 = qc.AllKeywords,
Parameter2 = keywords.ToArray(),
Parse = (input, values) =>
{
// Construct the regex pattern with capturing groups for keywords
Expand Down Expand Up @@ -111,7 +116,22 @@ public void ParseQuery(string query, string firstSelectQuery, int expectedQuerie
AssertValues(customParsingQc, query, firstSelectQuery, expectedQueries, queryTypes);
}

private void AssertValues(QueryConfiguration qc, string query, string firstSelectQuery, int expectedQueries, params QueryType[] queryTypes)
[Theory]
[InlineData(
"SHOW TABLES",
"SHOW TABLES",
1,
QueryReturnType.RecordSet
)]
public void ParseQueryWithCustomKeywords(string query, string firstSelectQuery, int expectedQueries, params QueryReturnType[] queryTypes)
{
QueryConfiguration qc = new QueryConfiguration();
qc.Keywords.Add("SHOW", QueryReturnType.RecordSet);

AssertValues(qc, query, firstSelectQuery, expectedQueries, queryTypes);
}

private void AssertValues(QueryConfiguration qc, string query, string firstSelectQuery, int expectedQueries, params QueryReturnType[] queryTypes)
{
QueryParser parser = new QueryParser(qc);

Expand All @@ -127,7 +147,7 @@ private void AssertValues(QueryConfiguration qc, string query, string firstSelec

Assert.True(q.Type == queryTypes[i], $"The value at {i} ({q.Type}) does not match the expected query type ({queryTypes[i]})");

if (q.Type == QueryType.Read && string.IsNullOrEmpty(firstFoundSelectQuery))
if (q.Type == QueryReturnType.RecordSet && string.IsNullOrEmpty(firstFoundSelectQuery))
{
firstFoundSelectQuery = q.Text.Trim();
}
Expand Down
Loading

0 comments on commit 84e3ecc

Please sign in to comment.