├── .gitignore ├── README.md ├── src ├── NReco.PrestoAdo │ ├── PrestoDbParameter.cs │ ├── PrestoDbFactory.cs │ ├── NReco.PrestoAdo.csproj │ ├── PrestoDbParameterCollection.cs │ ├── PrestoConnectionStringBuilder.cs │ ├── PrestoCommand.cs │ ├── PrestoConnection.cs │ └── PrestoDbDataReader.cs └── NReco.PrestoAdo.sln └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | *.suo 3 | *[Bb]in/ 4 | *[Oo]bj/ 5 | *[Dd]ebug/ 6 | *.user 7 | /src/packages 8 | *.lock.json 9 | *.nupkg 10 | *.dll 11 | *.targets 12 | project.lock.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NReco.PrestoAdo 2 | Presto/Trino ADO.NET Provider based on [PrestoClient](https://github.com/bamcis-io/PrestoClient). 3 | 4 | * uses Presto/Trino API v1 (headers prefix can be switched with "TrinoHeaders=1" in a connection string) 5 | * can connect to Metriql 6 | * compatible with .NET Core / NET6. 7 | 8 | Nuget package: [NReco.PrestoAdo](https://www.nuget.org/packages/NReco.PrestoAdo/) 9 | 10 | ## Connection string 11 | 12 | ``` 13 | Host=hostName;Port=8080;UseSsl=false;Schema=defaultSchema;Catalog=catalog;User=user;Password=password;TrinoHeaders=1; 14 | ``` 15 | 16 | ## Who is using this? 17 | NReco.PrestoAdo is in production use at [SeekTable.com](https://www.seektable.com/) and [PivotData microservice](https://www.nrecosite.com/pivotdata_service.aspx). 18 | 19 | ## License 20 | Copyright 2021-2022 Vitaliy Fedorchenko 21 | 22 | Distributed under the MIT license -------------------------------------------------------------------------------- /src/NReco.PrestoAdo/PrestoDbParameter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Common; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace NReco.PrestoAdo { 9 | 10 | public class PrestoDbParameter : DbParameter { 11 | public override DbType DbType { get; set; } 12 | 13 | public override ParameterDirection Direction { get => ParameterDirection.Input; set { } } 14 | 15 | public override bool IsNullable { get; set; } 16 | 17 | public override string ParameterName { get; set; } 18 | 19 | public override int Size { get; set; } 20 | 21 | public override string SourceColumn { get; set; } 22 | 23 | public override bool SourceColumnNullMapping { get; set; } 24 | 25 | public override object Value { get; set; } 26 | 27 | public override void ResetDbType() { } 28 | 29 | public override string ToString() => $"{ParameterName}:{Value}"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 NReco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/NReco.PrestoAdo.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31702.278 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NReco.PrestoAdo", "NReco.PrestoAdo\NReco.PrestoAdo.csproj", "{8FD66739-5274-46F1-9E94-DC619F92D04E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {8FD66739-5274-46F1-9E94-DC619F92D04E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {8FD66739-5274-46F1-9E94-DC619F92D04E}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {8FD66739-5274-46F1-9E94-DC619F92D04E}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {8FD66739-5274-46F1-9E94-DC619F92D04E}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {E89F9DF7-0F06-4B64-A155-30D620949959} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/NReco.PrestoAdo/PrestoDbFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Data.Common; 6 | 7 | namespace NReco.PrestoAdo { 8 | 9 | public sealed class PrestoDbFactory : DbProviderFactory { 10 | public static readonly PrestoDbFactory Instance = new PrestoDbFactory(); 11 | 12 | /// 13 | /// Returns a strongly typed instance. 14 | /// 15 | public override DbCommand CreateCommand() => new PrestoCommand(); 16 | 17 | /// 18 | /// Returns a strongly typed instance. 19 | /// 20 | public override DbConnection CreateConnection() => new PrestoConnection(); 21 | 22 | /// 23 | /// Returns a strongly typed instance. 24 | /// 25 | public override DbParameter CreateParameter() => new PrestoDbParameter(); 26 | 27 | /// 28 | /// Returns a strongly typed instance. 29 | /// 30 | public override DbConnectionStringBuilder CreateConnectionStringBuilder() => new PrestoConnectionStringBuilder(); 31 | 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/NReco.PrestoAdo/NReco.PrestoAdo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ADO.NET Provider for Presto/Trino. 5 | Presto/Trino .NET driver 6 | Copyright (c) 2022 Vitalii Fedorchenko 7 | NReco.PrestoAdo 8 | 1.1.0 9 | Vitalii Fedorchenko 10 | netstandard2.0 11 | true 12 | NReco.PrestoAdo 13 | true 14 | NReco.PrestoAdo 15 | Presto;PrestoDB;Trino;ADO 16 | Source code: https://github.com/nreco/presto-ado 17 | 18 | v.1.1 changes: 19 | - updated PrestoClient reference to the newest 0.351.0-beta (can switch between Presto/Trino headers prefix) 20 | - added a connection string property TrinoHeaders (bool, false by default for the backward compatibility) 21 | 22 | 23 | https://www.nrecosite.com/img/nreco-logo-200.png 24 | https://github.com/nreco/presto-ado 25 | https://raw.githubusercontent.com/nreco/presto-ado/master/LICENSE 26 | https://github.com/nreco/presto-ado 27 | git 28 | false 29 | false 30 | false 31 | false 32 | false 33 | false 34 | false 35 | false 36 | false 37 | false 38 | NReco.PrestoAdo.snk 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/NReco.PrestoAdo/PrestoDbParameterCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Common; 4 | using System.Collections; 5 | using System.Threading.Tasks; 6 | using System.Linq; 7 | 8 | namespace NReco.PrestoAdo { 9 | internal class PrestoDbParameterCollection : DbParameterCollection { 10 | private readonly List parameters = new List(); 11 | 12 | public override int Count => parameters.Count; 13 | 14 | public override object SyncRoot { get; } 15 | 16 | public override int Add(object value) { 17 | parameters.Add((PrestoDbParameter)value); 18 | return parameters.Count - 1; 19 | } 20 | 21 | public override void AddRange(Array values) => parameters.AddRange(values.Cast()); 22 | 23 | public override void Clear() => parameters.Clear(); 24 | 25 | public override bool Contains(object value) => parameters.Contains(value as PrestoDbParameter); 26 | 27 | public override bool Contains(string value) => parameters.Any(p => p.ParameterName == value); 28 | 29 | public override void CopyTo(Array array, int index) { 30 | for (int i = 0; i < parameters.Count; i++) { 31 | array.SetValue(parameters[i].Value, index + i); 32 | } 33 | } 34 | 35 | public override IEnumerator GetEnumerator() => parameters.GetEnumerator(); 36 | 37 | public override int IndexOf(object value) => parameters.IndexOf(value as PrestoDbParameter); 38 | 39 | public override int IndexOf(string parameterName) => parameters.FindIndex(x => x.ParameterName == parameterName); 40 | 41 | public override void Insert(int index, object value) => parameters.Insert(index, (PrestoDbParameter)value); 42 | 43 | public override void Remove(object value) => parameters.Remove(value as PrestoDbParameter); 44 | 45 | public override void RemoveAt(int index) => parameters.RemoveAt(index); 46 | 47 | public override void RemoveAt(string parameterName) => parameters.RemoveAll(p => p.ParameterName == parameterName); 48 | 49 | protected override DbParameter GetParameter(int index) => parameters[index]; 50 | 51 | protected override DbParameter GetParameter(string parameterName) => parameters[IndexOf(parameterName)]; 52 | 53 | protected override void SetParameter(int index, DbParameter value) => parameters[index] = (PrestoDbParameter)value; 54 | 55 | protected override void SetParameter(string parameterName, DbParameter value) { 56 | var index = IndexOf(parameterName); 57 | if (index < 0) 58 | Add(value); 59 | else 60 | SetParameter(index, value); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/NReco.PrestoAdo/PrestoConnectionStringBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Data.Common; 6 | 7 | namespace NReco.PrestoAdo { 8 | public class PrestoConnectionStringBuilder : DbConnectionStringBuilder { 9 | public PrestoConnectionStringBuilder() { 10 | } 11 | 12 | public PrestoConnectionStringBuilder(string connectionString) { 13 | ConnectionString = connectionString; 14 | } 15 | 16 | public string Host { 17 | get => TryGetValue("Host", out var value) ? value as string : "localhost"; 18 | set => this["Host"] = value; 19 | } 20 | 21 | public ushort Port { 22 | get => TryGetValue("Port", out var value) && value is string @string && ushort.TryParse(@string, out var @ushort) ? @ushort : (ushort)8080; 23 | set => this["Port"] = value; 24 | } 25 | 26 | public bool TrinoHeaders { 27 | get => TryGetValue("TrinoHeaders", out var value) ? IsYesTrueOne(value) : false; 28 | set => this["TrinoHeaders"] = value; 29 | } 30 | 31 | public string User { 32 | get => TryGetValue("User", out var value) ? value as string : String.Empty; 33 | set => this["User"] = value; 34 | } 35 | 36 | public string Password { 37 | get => TryGetValue("Password", out var value) ? value as string : string.Empty; 38 | set => this["Password"] = value; 39 | } 40 | 41 | public string Catalog { 42 | get => TryGetValue("Catalog", out var value) ? value as string : null; 43 | set => this["Catalog"] = value; 44 | } 45 | 46 | public string Schema { 47 | get => TryGetValue("Schema", out var value) ? value as string : "default"; 48 | set => this["Schema"] = value; 49 | } 50 | 51 | public int CheckInterval { 52 | get => TryGetValue("CheckInterval", out var value) && value is string @string && int.TryParse(@string, out var @int) ? @int : (int)800; 53 | set => this["CheckInterval"] = value; 54 | } 55 | 56 | public bool IgnoreSslErrors { 57 | get => TryGetValue("IgnoreSslErrors", out var value) ? IsYesTrueOne(value) : false; 58 | set => this["IgnoreSslErrors"] = value; 59 | } 60 | 61 | public bool UseSsl { 62 | get => TryGetValue("UseSsl", out var value) ? "true".Equals(value as string, StringComparison.OrdinalIgnoreCase) : false; 63 | set => this["UseSsl"] = value; 64 | } 65 | 66 | public int ClientRequestTimeout { 67 | get => TryGetValue("ClientRequestTimeout", out var value) && value is string @string && int.TryParse(@string, out var @int) ? @int : -1; 68 | set => this["ClientRequestTimeout"] = value; 69 | } 70 | 71 | bool IsYesTrueOne(object val) { 72 | var valStr = Convert.ToString(val); 73 | return valStr == "1" 74 | || "one".Equals(valStr, StringComparison.OrdinalIgnoreCase) 75 | || "true".Equals(valStr, StringComparison.OrdinalIgnoreCase); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/NReco.PrestoAdo/PrestoCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Data.Common; 6 | using System.Data; 7 | using System.Threading; 8 | using System.Text; 9 | 10 | namespace NReco.PrestoAdo { 11 | public class PrestoCommand : DbCommand { 12 | 13 | private readonly CancellationTokenSource cts = new CancellationTokenSource(); 14 | private readonly PrestoDbParameterCollection commandParameters = new PrestoDbParameterCollection(); 15 | private PrestoConnection connection; 16 | 17 | public PrestoCommand() { 18 | } 19 | 20 | public PrestoCommand(PrestoConnection connection) { 21 | this.connection = connection; 22 | } 23 | 24 | public override string CommandText { get; set; } 25 | 26 | public override int CommandTimeout { get; set; } 27 | 28 | public override CommandType CommandType { get; set; } 29 | 30 | public override bool DesignTimeVisible { get; set; } 31 | 32 | public override UpdateRowSource UpdatedRowSource { get; set; } 33 | 34 | protected override DbConnection DbConnection { 35 | get => connection; 36 | set => connection = (PrestoConnection)value; 37 | } 38 | 39 | protected override DbParameterCollection DbParameterCollection => commandParameters; 40 | 41 | protected override DbTransaction DbTransaction { get; set; } 42 | 43 | public new void Dispose() { 44 | cts?.Dispose(); 45 | base.Dispose(); 46 | } 47 | 48 | public override void Cancel() => cts.Cancel(); 49 | 50 | public override int ExecuteNonQuery() => ExecuteNonQueryAsync(CancellationToken.None).GetAwaiter().GetResult(); 51 | 52 | public override async Task ExecuteNonQueryAsync(CancellationToken cancellationToken) { 53 | if (connection == null) 54 | throw new InvalidOperationException("Connection is not set"); 55 | 56 | using (var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken)) { 57 | var result = await connection.ExecuteQueryAsync(this, linkedCancellationTokenSource.Token).ConfigureAwait(false); 58 | } 59 | // ExecuteNonQuery may be used for INSERTs, let's return 1 affected record 60 | return 1; 61 | } 62 | 63 | public override object ExecuteScalar() => ExecuteScalarAsync(CancellationToken.None).GetAwaiter().GetResult(); 64 | 65 | public override async Task ExecuteScalarAsync(CancellationToken cancellationToken) { 66 | using (var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken)) { 67 | using (var reader = await ExecuteDbDataReaderAsync(CommandBehavior.Default, linkedCancellationTokenSource.Token).ConfigureAwait(false)) { 68 | return reader.Read() ? reader.GetValue(0) : null; 69 | } 70 | } 71 | } 72 | 73 | public override void Prepare() { /* ClickHouse has no notion of prepared statements */ } 74 | 75 | public new PrestoDbParameter CreateParameter() => new PrestoDbParameter(); 76 | 77 | protected override DbParameter CreateDbParameter() => CreateParameter(); 78 | 79 | protected override void Dispose(bool disposing) { 80 | if (disposing) { 81 | cts.Dispose(); 82 | } 83 | } 84 | 85 | protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) => ExecuteDbDataReaderAsync(behavior, CancellationToken.None).GetAwaiter().GetResult(); 86 | 87 | protected override async Task ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) { 88 | if (connection == null) 89 | throw new InvalidOperationException("Connection is not set"); 90 | 91 | using (var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken)) { 92 | var sqlBuilder = new StringBuilder(CommandText); 93 | switch (behavior) { 94 | case CommandBehavior.SingleRow: 95 | case CommandBehavior.SingleResult: 96 | sqlBuilder.Append(" LIMIT 1"); 97 | break; 98 | case CommandBehavior.SchemaOnly: 99 | sqlBuilder.Append(" LIMIT 0"); 100 | break; 101 | default: 102 | break; 103 | } 104 | var result = await connection.ExecuteQueryAsync(this, linkedCancellationTokenSource.Token).ConfigureAwait(false); 105 | return new PrestoDbDataReader(result); 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/NReco.PrestoAdo/PrestoConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Data.Common; 6 | using System.Data; 7 | using BAMCIS.PrestoClient; 8 | using BAMCIS.PrestoClient.Model.Statement; 9 | using BAMCIS.PrestoClient.Model.Client; 10 | using System.Threading; 11 | using System.Text; 12 | 13 | namespace NReco.PrestoAdo { 14 | 15 | public class PrestoConnection : DbConnection { 16 | 17 | private PrestoClientSessionConfig config; 18 | private PrestodbClient client; 19 | private string connectionString; 20 | private ConnectionState state = ConnectionState.Closed; 21 | 22 | public PrestoConnection() 23 | : this(string.Empty) { 24 | } 25 | 26 | public PrestoConnection(string connectionString) { 27 | ConnectionString = connectionString; 28 | } 29 | 30 | public sealed override string ConnectionString { 31 | get { 32 | return connectionString; 33 | } 34 | set { 35 | var builder = new PrestoConnectionStringBuilder(value); 36 | config = new PrestoClientSessionConfig() { 37 | Host = builder.Host, 38 | Port = builder.Port, 39 | Catalog = builder.Catalog, 40 | Schema = builder.Schema, 41 | CheckInterval = builder.CheckInterval, 42 | IgnoreSslErrors = builder.IgnoreSslErrors, 43 | UseSsl = builder.UseSsl, 44 | ClientRequestTimeout = builder.ClientRequestTimeout, 45 | EnableLegacyHeaderCompatibility = !builder.TrinoHeaders 46 | }; 47 | if (!String.IsNullOrEmpty(builder.User)) 48 | config.User = builder.User; 49 | if (!String.IsNullOrEmpty(builder.Password)) 50 | config.Password = builder.Password; 51 | 52 | client = new PrestodbClient(config); 53 | } 54 | } 55 | 56 | public override ConnectionState State => state; 57 | 58 | public override string Database => config?.Catalog; 59 | 60 | public override void Close() => state = ConnectionState.Closed; 61 | 62 | public override string DataSource => ""; 63 | 64 | public override string ServerVersion => ""; 65 | 66 | public override void Open() => state = ConnectionState.Open; 67 | 68 | public override void ChangeDatabase(string databaseName) { 69 | throw new NotSupportedException(); 70 | } 71 | 72 | public override async Task OpenAsync(System.Threading.CancellationToken token) { 73 | if (State == ConnectionState.Open) 74 | return; 75 | state = ConnectionState.Open; 76 | } 77 | 78 | public new PrestoCommand CreateCommand() => new PrestoCommand(this); 79 | 80 | protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) => throw new NotSupportedException(); 81 | 82 | protected override DbCommand CreateDbCommand() => CreateCommand(); 83 | 84 | internal async Task ExecuteQueryAsync(PrestoCommand cmd, CancellationToken ct) { 85 | var sqlText = cmd.CommandText; 86 | var cmdParams = new Dictionary(cmd.Parameters.Count); 87 | foreach (DbParameter p in cmd.Parameters) { 88 | cmdParams[p.ParameterName] = "'"+Convert.ToString(p.Value, System.Globalization.CultureInfo.InvariantCulture).Replace("'", "''")+"'"; 89 | } 90 | ExecuteQueryV1Request request = new ExecuteQueryV1Request(SubstituteParameters(sqlText, cmdParams)); 91 | ExecuteQueryV1Response queryResponse = await client.ExecuteQueryV1(request, ct); 92 | return new PrestoCommandResults(queryResponse.Columns, () => queryResponse.Data); 93 | } 94 | 95 | private static string SubstituteParameters(string query, IDictionary parameters) { 96 | var builder = new StringBuilder(query.Length); 97 | var paramStartPos = query.IndexOf('{'); 98 | var paramEndPos = -1; 99 | while (paramStartPos != -1) { 100 | builder.Append(query.Substring(paramEndPos + 1, paramStartPos - paramEndPos - 1)); 101 | 102 | paramStartPos += 1; 103 | paramEndPos = query.IndexOf('}', paramStartPos); 104 | var param = query.Substring(paramStartPos, paramEndPos - paramStartPos); 105 | 106 | if (!parameters.TryGetValue(param, out var value)) { 107 | // doesn't match a parameter. leave as is 108 | value = "{" + param + "}"; 109 | } 110 | 111 | builder.Append(value); 112 | paramStartPos = query.IndexOf('{', paramEndPos); 113 | } 114 | builder.Append(query.Substring(paramEndPos + 1, query.Length - paramEndPos - 1)); 115 | return builder.ToString(); 116 | } 117 | 118 | internal class PrestoCommandResults { 119 | 120 | internal IReadOnlyList Columns { get; private set; } 121 | 122 | Func>> getData; 123 | 124 | public PrestoCommandResults(IReadOnlyList cols, Func>> getData) { 125 | Columns = cols; 126 | this.getData = getData; 127 | } 128 | 129 | public IEnumerable> GetData() { 130 | return getData(); 131 | } 132 | } 133 | 134 | } 135 | 136 | 137 | } 138 | -------------------------------------------------------------------------------- /src/NReco.PrestoAdo/PrestoDbDataReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Globalization; 6 | using System.Threading.Tasks; 7 | using System.Data.Common; 8 | using System.Data; 9 | using BAMCIS.PrestoClient; 10 | using BAMCIS.PrestoClient.Serialization; 11 | using BAMCIS.PrestoClient.Model.Client; 12 | using System.Threading; 13 | 14 | namespace NReco.PrestoAdo { 15 | 16 | public class PrestoDbDataReader : DbDataReader { 17 | 18 | PrestoConnection.PrestoCommandResults qResults; 19 | Column[] Columns; 20 | IEnumerator> DataEnumerator; 21 | 22 | protected List CurrentRow { get; set; } 23 | 24 | protected string[] FieldNames { get; set; } 25 | 26 | IReadOnlyDictionary FieldNameToIndex; 27 | 28 | internal PrestoDbDataReader(PrestoConnection.PrestoCommandResults results) { 29 | qResults = results; 30 | Columns = qResults.Columns.ToArray(); 31 | FieldNames = Columns.Select(c => c.Name).ToArray(); 32 | var fldNameToIdx = new Dictionary(FieldNames.Length); 33 | for (int i = 0; i < FieldNames.Length; i++) 34 | fldNameToIdx[FieldNames[i]] = i; 35 | FieldNameToIndex = fldNameToIdx; 36 | } 37 | 38 | public override object this[int ordinal] => GetValue(ordinal); 39 | 40 | public override object this[string name] => this[GetOrdinal(name)]; 41 | 42 | public override int Depth { get; } 43 | 44 | public override int FieldCount => Columns.Length; 45 | 46 | public override bool IsClosed => false; 47 | 48 | public sealed override bool HasRows => true; 49 | 50 | public override int RecordsAffected { get; } 51 | 52 | public override bool GetBoolean(int ordinal) => Convert.ToBoolean(GetValue(ordinal), CultureInfo.InvariantCulture); 53 | 54 | public override byte GetByte(int ordinal) => (byte)GetValue(ordinal); 55 | 56 | public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) => throw new NotImplementedException(); 57 | 58 | public override char GetChar(int ordinal) => (char)GetValue(ordinal); 59 | 60 | public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length) => throw new NotImplementedException(); 61 | 62 | public override string GetDataTypeName(int ordinal) => Columns[ordinal].Type; 63 | 64 | public override DateTime GetDateTime(int ordinal) => (DateTime)GetValue(ordinal); 65 | 66 | public override decimal GetDecimal(int ordinal) => (decimal)GetValue(ordinal); 67 | 68 | public override double GetDouble(int ordinal) => (double)GetValue(ordinal); 69 | 70 | public override IEnumerator GetEnumerator() => CurrentRow.GetEnumerator(); 71 | 72 | public override Type GetFieldType(int ordinal) { 73 | var col = Columns[ordinal]; 74 | if (PrestoTypeMapping.Types.TryGetValue(col.Type.ToLower(), out var mappingInfo)) 75 | return mappingInfo.DotNetType; 76 | return typeof(object); 77 | } 78 | 79 | public override float GetFloat(int ordinal) => (float)GetValue(ordinal); 80 | 81 | public override Guid GetGuid(int ordinal) => (Guid)GetValue(ordinal); 82 | 83 | public override short GetInt16(int ordinal) => (short)GetValue(ordinal); 84 | 85 | public override int GetInt32(int ordinal) => (int)GetValue(ordinal); 86 | 87 | public override long GetInt64(int ordinal) => (long)GetValue(ordinal); 88 | 89 | public override string GetName(int ordinal) => FieldNames[ordinal]; 90 | 91 | public override int GetOrdinal(string name) { 92 | if (FieldNameToIndex.TryGetValue(name, out var idx)) 93 | return idx; 94 | throw new IndexOutOfRangeException($"Unknown column '{name}'"); 95 | } 96 | 97 | public override string GetString(int ordinal) => (string)GetValue(ordinal); 98 | 99 | public override object GetValue(int ordinal) => CurrentRow[ordinal]; 100 | 101 | public override int GetValues(object[] values) { 102 | if (CurrentRow == null) { 103 | throw new InvalidOperationException(); 104 | } 105 | CurrentRow.CopyTo(values, 0); 106 | return CurrentRow.Count; 107 | } 108 | 109 | public override bool IsDBNull(int ordinal) => GetValue(ordinal) is DBNull || GetValue(ordinal) is null; 110 | 111 | public override bool NextResult() => false; 112 | 113 | public override void Close() => Dispose(); 114 | 115 | public override T GetFieldValue(int ordinal) => (T)GetValue(ordinal); 116 | 117 | DataTable _SchemaTable = null; 118 | 119 | public override DataTable GetSchemaTable() { 120 | return _SchemaTable ?? (_SchemaTable = BuildSchemaTable()); 121 | } 122 | 123 | internal DataTable BuildSchemaTable() { 124 | var schemaTable = new DataTable("SchemaTable"); 125 | 126 | var ColumnName = new DataColumn(SchemaTableColumn.ColumnName, typeof(string)); 127 | var ColumnOrdinal = new DataColumn(SchemaTableColumn.ColumnOrdinal, typeof(int)); 128 | var ColumnSize = new DataColumn(SchemaTableColumn.ColumnSize, typeof(int)); 129 | var NumericPrecision = new DataColumn(SchemaTableColumn.NumericPrecision, typeof(short)); 130 | var NumericScale = new DataColumn(SchemaTableColumn.NumericScale, typeof(short)); 131 | 132 | var DataType = new DataColumn(SchemaTableColumn.DataType, typeof(Type)); 133 | var DataTypeName = new DataColumn("DataTypeName", typeof(string)); 134 | 135 | var IsLong = new DataColumn(SchemaTableColumn.IsLong, typeof(bool)); 136 | var AllowDBNull = new DataColumn(SchemaTableColumn.AllowDBNull, typeof(bool)); 137 | 138 | var IsUnique = new DataColumn(SchemaTableColumn.IsUnique, typeof(bool)); 139 | var IsKey = new DataColumn(SchemaTableColumn.IsKey, typeof(bool)); 140 | var IsAutoIncrement = new DataColumn(SchemaTableOptionalColumn.IsAutoIncrement, typeof(bool)); 141 | 142 | var BaseCatalogName = new DataColumn(SchemaTableOptionalColumn.BaseCatalogName, typeof(string)); 143 | var BaseSchemaName = new DataColumn(SchemaTableColumn.BaseSchemaName, typeof(string)); 144 | var BaseTableName = new DataColumn(SchemaTableColumn.BaseTableName, typeof(string)); 145 | var BaseColumnName = new DataColumn(SchemaTableColumn.BaseColumnName, typeof(string)); 146 | 147 | var BaseServerName = new DataColumn(SchemaTableOptionalColumn.BaseServerName, typeof(string)); 148 | var IsAliased = new DataColumn(SchemaTableColumn.IsAliased, typeof(bool)); 149 | var IsExpression = new DataColumn(SchemaTableColumn.IsExpression, typeof(bool)); 150 | 151 | var columns = schemaTable.Columns; 152 | 153 | columns.Add(ColumnName); 154 | columns.Add(ColumnOrdinal); 155 | columns.Add(ColumnSize); 156 | columns.Add(NumericPrecision); 157 | columns.Add(NumericScale); 158 | columns.Add(IsUnique); 159 | columns.Add(IsKey); 160 | columns.Add(BaseServerName); 161 | columns.Add(BaseCatalogName); 162 | columns.Add(BaseColumnName); 163 | columns.Add(BaseSchemaName); 164 | columns.Add(BaseTableName); 165 | columns.Add(DataType); 166 | columns.Add(DataTypeName); 167 | columns.Add(AllowDBNull); 168 | columns.Add(IsAliased); 169 | columns.Add(IsExpression); 170 | columns.Add(IsAutoIncrement); 171 | columns.Add(IsLong); 172 | 173 | for (int i = 0; i < Columns.Length; i++) { 174 | var schemaRow = schemaTable.NewRow(); 175 | 176 | schemaRow[ColumnName] = Columns[i].Name; 177 | schemaRow[ColumnOrdinal] = i; 178 | schemaRow[ColumnSize] = DBNull.Value; 179 | schemaRow[NumericPrecision] = DBNull.Value; 180 | schemaRow[NumericScale] = DBNull.Value; 181 | schemaRow[BaseServerName] = DBNull.Value; 182 | schemaRow[BaseCatalogName] = DBNull.Value; 183 | schemaRow[BaseColumnName] = Columns[i].Name; 184 | schemaRow[BaseSchemaName] = DBNull.Value; 185 | schemaRow[BaseTableName] = DBNull.Value; 186 | schemaRow[DataType] = GetFieldType(i); 187 | schemaRow[DataTypeName] = Columns[i].Type; 188 | schemaRow[IsAliased] = false; 189 | schemaRow[IsExpression] = false; 190 | schemaRow[IsLong] = DBNull.Value; 191 | schemaRow[IsKey] = false; 192 | schemaRow[AllowDBNull] = true; 193 | schemaRow[IsAutoIncrement] = false; 194 | 195 | schemaTable.Rows.Add(schemaRow); 196 | } 197 | return schemaTable; 198 | } 199 | 200 | public override Task NextResultAsync(CancellationToken cancellationToken) => Task.FromResult(false); 201 | 202 | // Custom extension 203 | public ushort GetUInt16(int ordinal) => (ushort)GetValue(ordinal); 204 | 205 | // Custom extension 206 | public uint GetUInt32(int ordinal) => (uint)GetValue(ordinal); 207 | 208 | // Custom extension 209 | public ulong GetUInt64(int ordinal) => (ulong)GetValue(ordinal); 210 | 211 | public override bool Read() { 212 | if (DataEnumerator == null) 213 | DataEnumerator = qResults.GetData().GetEnumerator(); 214 | if (!DataEnumerator.MoveNext()) { 215 | CurrentRow = null; 216 | 217 | return false; 218 | } 219 | CurrentRow = DataEnumerator.Current; 220 | return true; 221 | } 222 | 223 | protected override void Dispose(bool disposing) { 224 | if (DataEnumerator != null) { 225 | DataEnumerator.Dispose(); 226 | DataEnumerator = null; 227 | } 228 | } 229 | 230 | } 231 | 232 | } 233 | --------------------------------------------------------------------------------