Summary

Class:CBAM.SQL.Implementation.AbstractCommandExecutionResult
Assembly:CBAM.SQL.Implementation
File(s):/repo-dir/contents/Source/Code/CBAM.SQL.Implementation/Connection.cs
Covered lines:7
Uncovered lines:2
Coverable lines:9
Total lines:407
Line coverage:77.7%
Branch coverage:0%

Coverage History

Metrics

MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage
.ctor(...)101%0%

File(s)

/repo-dir/contents/Source/Code/CBAM.SQL.Implementation/Connection.cs

#LineLine coverage
 1/*
 2 * Copyright 2017 Stanislav Muhametsin. All rights Reserved.
 3 *
 4 * Licensed  under the  Apache License,  Version 2.0  (the "License");
 5 * you may not use  this file  except in  compliance with the License.
 6 * You may obtain a copy of the License at
 7 *
 8 *   http://www.apache.org/licenses/LICENSE-2.0
 9 *
 10 * Unless required by applicable law or agreed to in writing, software
 11 * distributed  under the  License is distributed on an "AS IS" BASIS,
 12 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
 13 * implied.
 14 *
 15 * See the License for the specific language governing permissions and
 16 * limitations under the License.
 17 */
 18using CBAM.Abstractions;
 19using CBAM.Abstractions.Implementation;
 20using CBAM.SQL;
 21using CBAM.SQL.Implementation;
 22using System;
 23using System.Collections.Generic;
 24using System.Text;
 25using System.Threading;
 26using System.Threading.Tasks;
 27using UtilPack;
 28using UtilPack.TabularData;
 29
 30namespace CBAM.SQL.Implementation
 31{
 32   /// <summary>
 33   /// This class extends <see cref="ConnectionImpl{TStatement, TStatementInformation, TStatementCreationArgs, TEnumerab
 34   /// </summary>
 35   /// <typeparam name="TConnectionFunctionality">The type of object actually implementing the functionality for this fa
 36   /// <typeparam name="TVendor">The actual type of vendor.</typeparam>
 37   public abstract class SQLConnectionImpl<TConnectionFunctionality, TVendor> : ConnectionImpl<SQLStatementBuilder, SQLS
 38      where TConnectionFunctionality : DefaultConnectionFunctionality<SQLStatementBuilder, SQLStatementBuilderInformatio
 39      where TVendor : SQLConnectionVendorFunctionality
 40   {
 41      private Object _isReadOnly;
 42      private Object _isolationLevel;
 43
 44      /// <summary>
 45      /// Creates a new instance of <see cref="SQLConnectionImpl{TConnectionFunctionality, TVendor}"/> with given parame
 46      /// </summary>
 47      /// <param name="connectionFunctionality">The object containing the actual <see cref="Connection{TStatement, TStat
 48      /// <param name="metaData">The <see cref="SQL.DatabaseMetadata"/> object containing metadata functionality.</param
 49      /// <exception cref="ArgumentNullException">If either of <paramref name="connectionFunctionality"/> or <paramref n
 50      public SQLConnectionImpl(
 51         TConnectionFunctionality connectionFunctionality,
 52         DatabaseMetadata metaData
 53         ) : base( connectionFunctionality )
 54      {
 55         this.DatabaseMetadata = ArgumentValidator.ValidateNotNull( nameof( metaData ), metaData );
 56      }
 57
 58
 59      /// <summary>
 60      /// Implements <see cref="SQLConnection.DatabaseMetadata"/> and gets the <see cref="SQL.DatabaseMetadata"/> object
 61      /// </summary>
 62      /// <value>The <see cref="SQL.DatabaseMetadata"/> object of this connection.</value>
 63      public DatabaseMetadata DatabaseMetadata { get; }
 64
 65      /// <summary>
 66      /// Implements <see cref="SQLConnection.GetReadOnlyAsync"/> and asynchronously gets value indicating whether this 
 67      /// </summary>
 68      /// <returns>Asynchronously returns value indicating whether this connection is in read-only mode.</returns>
 69      public async ValueTask<Boolean> GetReadOnlyAsync()
 70      {
 71         if ( !this.IsReadOnlyProperty.HasValue )
 72         {
 73            this.IsReadOnlyProperty = await this.GetFirstOrDefaultAsync( this.GetSQLForGettingReadOnly(), extractor: thi
 74         }
 75         return this.IsReadOnlyProperty.Value;
 76      }
 77
 78      /// <summary>
 79      /// Implements <see cref="SQLConnection.GetDefaultTransactionIsolationLevelAsync"/> and asynchronously gets value 
 80      /// </summary>
 81      /// <returns>Asynchronously returns value indicating current default transaction isolation level of this connectio
 82      /// <seealso cref="TransactionIsolationLevel"/>
 83      public async ValueTask<TransactionIsolationLevel> GetDefaultTransactionIsolationLevelAsync()
 84      {
 85         if ( !this.TransactionIsolationLevelProperty.HasValue )
 86         {
 87            this.TransactionIsolationLevelProperty = await this.GetFirstOrDefaultAsync( this.GetSQLForGettingTransaction
 88         }
 89         return this.TransactionIsolationLevelProperty.Value;
 90      }
 91
 92      /// <summary>
 93      /// Implements <see cref="SQLConnection.SetDefaultTransactionIsolationLevelAsync(TransactionIsolationLevel)"/> and
 94      /// </summary>
 95      /// <param name="level">The new <see cref="TransactionIsolationLevel"/> to set.</param>
 96      /// <returns>Asynchronously returns either <c>-1</c>, if current transaction isolation level is already same as gi
 97      public ValueTask<Int64> SetDefaultTransactionIsolationLevelAsync( TransactionIsolationLevel level )
 98      {
 99         var propValue = this.TransactionIsolationLevelProperty;
 100         ValueTask<Int64> retVal;
 101         if ( !propValue.HasValue || propValue.Value != level )
 102         {
 103            retVal = this.ExecuteAndIgnoreResults( this.GetSQLForSettingTransactionIsolationLevel( level ), () => this.T
 104         }
 105         else
 106         {
 107            retVal = new ValueTask<Int64>( -1 );
 108         }
 109
 110         return retVal;
 111      }
 112
 113      /// <summary>
 114      /// Implements <see cref="SQLConnection.SetReadOnlyAsync(Boolean)"/> and asynchronously sets connection read-only 
 115      /// </summary>
 116      /// <param name="isReadOnly">Whether connection should be in read-only mode.</param>
 117      /// <returns>Asynchronously returns either <c>-1</c>, if current read-only mode is same as given <paramref name="i
 118      public ValueTask<Int64> SetReadOnlyAsync( Boolean isReadOnly )
 119      {
 120         var propValue = this.IsReadOnlyProperty;
 121         ValueTask<Int64> retVal;
 122         if ( !propValue.HasValue || propValue.Value != isReadOnly )
 123         {
 124
 125            retVal = this.ExecuteAndIgnoreResults( this.GetSQLForSettingReadOnly( isReadOnly ), () => this.IsReadOnlyPro
 126         }
 127         else
 128         {
 129            retVal = new ValueTask<Int64>( -1 );
 130         }
 131
 132         return retVal;
 133      }
 134
 135      /// <inheritdoc />
 136      public abstract ValueTask<Boolean> ProcessStatementResultPassively( MemorizingPotentiallyAsyncReader<Char?, Char> 
 137
 138      /// <summary>
 139      /// Gets the last seen value of read-only mode.
 140      /// </summary>
 141      /// <value>The last seen value of read-only mode.</value>
 142      protected Boolean? IsReadOnlyProperty
 143      {
 144         get
 145         {
 146            return (Boolean?) this._isReadOnly;
 147         }
 148         set
 149         {
 150            Interlocked.Exchange( ref this._isReadOnly, value );
 151         }
 152      }
 153
 154      /// <summary>
 155      /// Gets the last seen value of default transaction isolation level.
 156      /// </summary>
 157      /// <value>The last seen value of default transaction isolation level.</value>
 158      protected TransactionIsolationLevel? TransactionIsolationLevelProperty
 159      {
 160         get
 161         {
 162            return (TransactionIsolationLevel?) this._isolationLevel;
 163         }
 164         set
 165         {
 166            Interlocked.Exchange( ref this._isolationLevel, value );
 167         }
 168      }
 169
 170      /// <summary>
 171      /// This method should return SQL statement that is executed in order to get current default transaction isolation
 172      /// </summary>
 173      /// <returns>SQL statement to get current default transaction isolation level.</returns>
 174      protected abstract String GetSQLForGettingTransactionIsolationLevel();
 175
 176      /// <summary>
 177      /// This method should return SQL statement that is executed in order to set current default transaction isolation
 178      /// </summary>
 179      /// <param name="level">The isolation level to set.</param>
 180      /// <returns>SQL statement to set current default transaction isolation level.</returns>
 181      /// <remarks>
 182      /// The returned SQL statement should not be prepared statement - i.e., it should not have parameters.
 183      /// </remarks>
 184      protected abstract String GetSQLForSettingTransactionIsolationLevel( TransactionIsolationLevel level );
 185
 186      /// <summary>
 187      /// This method should return SQL statement that is executed in order to get connection read-only mode.
 188      /// </summary>
 189      /// <returns>SQL statement to get current read-only mode.</returns>
 190      protected abstract String GetSQLForGettingReadOnly();
 191
 192      /// <summary>
 193      /// This method should return SQL statement that is executed in order to set current read-only mode.
 194      /// </summary>
 195      /// <param name="isReadOnly">The read-only mode to set.</param>
 196      /// <returns>SQL statement to set current connection read-only mode.</returns>
 197      /// <remarks>
 198      /// The returned SLQ statement should not be prepared statement - i.e., it should not have parameters.
 199      /// </remarks>
 200      protected abstract String GetSQLForSettingReadOnly( Boolean isReadOnly );
 201
 202      /// <summary>
 203      /// This method should interpret the value returned by executing SQL of <see cref="GetSQLForGettingReadOnly"/>.
 204      /// </summary>
 205      /// <param name="row">The row returned by executing SQL of <see cref="GetSQLForGettingReadOnly"/>.</param>
 206      /// <returns>The value indicating whether connection is in read-only mode.</returns>
 207      protected abstract ValueTask<Boolean> InterpretReadOnly( AsyncDataColumn row );
 208
 209      /// <summary>
 210      /// This method should interpret the value returned by executing SQL of <see cref="GetSQLForGettingTransactionIsol
 211      /// </summary>
 212      /// <param name="row">The row returned by executing SQL of <see cref="GetSQLForGettingTransactionIsolationLevel"/>
 213      /// <returns>The <see cref="TransactionIsolationLevel"/> enumeration value.</returns>
 214      protected abstract ValueTask<TransactionIsolationLevel> InterpretTransactionIsolationLevel( AsyncDataColumn row );
 215   }
 216
 217   /// <summary>
 218   /// This class provides implementation of <see cref="SQLConnectionVendorFunctionality"/> which should be the same for
 219   /// </summary>
 220   public abstract class DefaultConnectionVendorFunctionality : SQLConnectionVendorFunctionality
 221   {
 222
 223      /// <inheritdoc />
 224      public abstract String EscapeLiteral( String str );
 225
 226      /// <summary>
 227      /// Implements <see cref="ConnectionVendorFunctionality{TStatement, TStatementCreationArgs}.CreateStatementBuilder
 228      /// </summary>
 229      /// <param name="sql">The textual SQL statement.</param>
 230      /// <returns>Will return <c>null</c> if <paramref name="sql"/> is <c>null</c> or empty, or can not be parsed into 
 231      public SQLStatementBuilder CreateStatementBuilder( String sql )
 232      {
 233         SQLStatementBuilder retVal;
 234         if ( !String.IsNullOrEmpty( sql ) )
 235         {
 236            var start = 0;
 237            var count = sql.Length;
 238            // Trim begin
 239            while ( count > 0 && this.CanTrimBegin( sql[start] ) )
 240            {
 241               ++start;
 242               --count;
 243            }
 244            // Trim end
 245            while ( count > 0 && this.CanTrimEnd( sql[start + count - 1] ) )
 246            {
 247               --count;
 248            }
 249
 250            if ( start > 0 || count < sql.Length )
 251            {
 252               sql = new String( sql.ToCharArray(), start, count );
 253            }
 254            retVal = this.TryParseStatementSQL( sql, out var parameterIndices ) ?
 255               this.CreateStatementBuilder( sql, parameterIndices ) :
 256               null;
 257         }
 258         else
 259         {
 260            retVal = null;
 261         }
 262         return retVal;
 263      }
 264
 265      /// <summary>
 266      /// Provides default implementation for <see cref="SQLConnectionVendorFunctionality.CanTrimBegin(Char)"/> and retu
 267      /// </summary>
 268      /// <param name="c">The character to check.</param>
 269      /// <returns><c>true</c> if <see cref="Char.IsWhiteSpace(Char)"/> returns <c>true</c>.</returns>
 270      /// <remarks>
 271      /// Subclasses may override this method.
 272      /// </remarks>
 273      public virtual Boolean CanTrimBegin( Char c )
 274      {
 275         return Char.IsWhiteSpace( c );
 276      }
 277
 278      /// <summary>
 279      /// Provides default implementation for <see cref="SQLConnectionVendorFunctionality.CanTrimEnd(Char)"/> and return
 280      /// </summary>
 281      /// <param name="c">The character to check.</param>
 282      /// <returns><c>true</c> if <see cref="Char.IsWhiteSpace(Char)"/> returns <c>true</c>, or if <paramref name="c"/> 
 283      /// <remarks>
 284      /// Subclasses may override this method.
 285      /// </remarks>
 286      public virtual Boolean CanTrimEnd( Char c )
 287      {
 288         return this.CanTrimBegin( c ) || c == ';';
 289      }
 290
 291      /// <summary>
 292      /// This method is called by <see cref="CreateStatementBuilder(String)"/> and should try to parse textual SQL stri
 293      /// </summary>
 294      /// <param name="sql">The textual SQL to parse.</param>
 295      /// <param name="parameterIndices">This parameter should have indices of legal parameter characters (<c>?</c>) in 
 296      /// <returns><c>true</c> if <paramref name="sql"/> at least looks like valid SQL; <c>false</c> otherwise.</returns
 297      protected abstract Boolean TryParseStatementSQL( String sql, out Int32[] parameterIndices );
 298
 299      /// <summary>
 300      /// This method should actually create instance of <see cref="SQLStatementBuilder"/> once SQL has been parsed and 
 301      /// </summary>
 302      /// <param name="sql">The textual SQL statement.</param>
 303      /// <param name="parameterIndices">The indices of legal parameter characters (<c>?</c>) in <paramref name="sql"/>.
 304      /// <returns>A new instance of <see cref="SQLStatementBuilder"/>.</returns>
 305      protected abstract SQLStatementBuilder CreateStatementBuilder( String sql, Int32[] parameterIndices );
 306
 307      /// <inheritdoc/>
 308      public abstract ValueTask<Boolean> TryAdvanceReaderOverSingleStatement( PeekablePotentiallyAsyncReader<Char?> read
 309
 310   }
 311
 312   /// <summary>
 313   /// This class provides default implementation for <see cref="SQLStatementExecutionResult"/>.
 314   /// </summary>
 315   public abstract class AbstractCommandExecutionResult : SQLStatementExecutionResult
 316   {
 317      private readonly Lazy<SQLException[]> _warnings;
 318
 319      /// <summary>
 320      /// Initializes a new instance of <see cref="AbstractCommandExecutionResult"/> with given parameters.
 321      /// </summary>
 322      /// <param name="commandTag">Textual SQL command tag, if any. May be <c>null</c>.</param>
 323      /// <param name="warnings">The lazily initialized <see cref="Lazy{T}"/> to get occurred warnings. May be <c>null</
 6324      public AbstractCommandExecutionResult(
 6325         String commandTag,
 6326         Lazy<SQLException[]> warnings
 6327         )
 328      {
 6329         this.CommandTag = commandTag;
 6330         this._warnings = warnings;
 6331      }
 332
 333      /// <summary>
 334      /// Gets the SQL command tag, if such was supplied.
 335      /// </summary>
 336      /// <value>The SQL command tag or <c>null</c>.</value>
 0337      public String CommandTag { get; }
 338
 339      /// <summary>
 340      /// Implements <see cref="SQLStatementExecutionResult.Warnings"/> and gets the warnings related to previous SQL co
 341      /// </summary>
 342      /// <value>The warnings related to previous SQL command execution, or empty array if none occurred.</value>
 343      public SQLException[] Warnings
 344      {
 345         get
 346         {
 0347            return this._warnings?.Value ?? Empty<SQLException>.Array;
 348         }
 349      }
 350
 351   }
 352
 353   /// <summary>
 354   /// This class provides default implementation for <see cref="SingleCommandExecutionResult"/> by extending <see cref=
 355   /// </summary>
 356   public sealed class SingleCommandExecutionResultImpl : AbstractCommandExecutionResult, SingleCommandExecutionResult
 357   {
 358      /// <summary>
 359      /// Creates a new instance of <see cref="SingleCommandExecutionResultImpl"/> with given parameters.
 360      /// </summary>
 361      /// <param name="commandTag">Textual SQL command tag, if any. May be <c>null</c>.</param>
 362      /// <param name="warnings">The lazily initialized <see cref="Lazy{T}"/> to get occurred warnings. May be <c>null</
 363      /// <param name="affectedRows">How many rows were affected by this single command.</param>
 364      public SingleCommandExecutionResultImpl(
 365         String commandTag,
 366         Lazy<SQLException[]> warnings,
 367         Int32 affectedRows
 368         ) : base( commandTag, warnings )
 369      {
 370         this.AffectedRows = affectedRows;
 371      }
 372
 373      /// <summary>
 374      /// Implements <see cref="SingleCommandExecutionResult.AffectedRows"/> and gets the amount of rows affected by sin
 375      /// </summary>
 376      /// <value>The amount of rows affected by single command.</value>
 377      public Int32 AffectedRows { get; }
 378   }
 379
 380   /// <summary>
 381   /// This class provides default implementation for <see cref="BatchCommandExecutionResult"/> by extending <see cref="
 382   /// </summary>
 383   public sealed class BatchCommandExecutionResultImpl : AbstractCommandExecutionResult, BatchCommandExecutionResult
 384   {
 385      /// <summary>
 386      /// Creates a new instane of <see cref="BatchCommandExecutionResultImpl"/> with given parameters.
 387      /// </summary>
 388      /// <param name="commandTag">Textual SQL command tag, if any. May be <c>null</c>.</param>
 389      /// <param name="warnings">The lazily initialized <see cref="Lazy{T}"/> to get occurred warnings. May be <c>null</
 390      /// <param name="affectedRows">The array indicating amount of affected rows for each item in the batch.</param>
 391      public BatchCommandExecutionResultImpl(
 392         String commandTag,
 393         Lazy<SQLException[]> warnings,
 394         Int32[] affectedRows
 395         ) : base( commandTag, warnings )
 396      {
 397         this.AffectedRows = affectedRows;
 398      }
 399
 400      /// <summary>
 401      /// Implements <see cref="BatchCommandExecutionResult.AffectedRows"/> and gets the amount of rows affected by each
 402      /// </summary>
 403      /// <value>The amount of rows affected by each executed SQL statement.</value>
 404      public Int32[] AffectedRows { get; }
 405   }
 406
 407}