Summary

Class:CBAM.SQL.PostgreSQL.Implementation.PgSQLConnectionVendorFunctionalityImpl
Assembly:CBAM.SQL.PostgreSQL.Implementation
File(s):/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL.Implementation/Connection.cs
Covered lines:27
Uncovered lines:13
Coverable lines:40
Total lines:342
Line coverage:67.5%
Branch coverage:57.6%

Coverage History

Metrics

MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage
.ctor()101%0%
EscapeLiteral(...)1400.308%0.357%
CreateStatementBuilder(...)201%1%
TryParseStatementSQL(...)801%1%
TryAdvanceReaderOverSingleStatement()200%0%
TryAdvanceReaderOverCopyInStatement(...)100%0%

File(s)

/repo-dir/contents/Source/Code/CBAM.SQL.PostgreSQL.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.SQL.Implementation;
 19using System;
 20using System.Collections.Generic;
 21using System.Text;
 22using System.Threading.Tasks;
 23using System.IO;
 24using System.Threading;
 25using UtilPack;
 26using CBAM.Abstractions.Implementation;
 27using UtilPack.TabularData;
 28using System.Net;
 29
 30#if !NETSTANDARD1_0
 31using IOUtils.Network.ResourcePooling;
 32#endif
 33
 34namespace CBAM.SQL.PostgreSQL.Implementation
 35{
 36   internal sealed class PgSQLConnectionImpl : SQLConnectionImpl<PostgreSQLProtocol, PgSQLConnectionVendorFunctionality>
 37   {
 38      private const String TRANSACTION_ISOLATION_PREFIX = "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL ";
 39
 40      private const String READ_UNCOMMITTED = TRANSACTION_ISOLATION_PREFIX + "READ UNCOMMITTED";
 41      private const String READ_COMMITTED = TRANSACTION_ISOLATION_PREFIX + "READ COMMITTED";
 42      private const String REPEATABLE_READ = TRANSACTION_ISOLATION_PREFIX + "REPEATABLE READ";
 43      private const String SERIALIZABLE = TRANSACTION_ISOLATION_PREFIX + "SERIALIZABLE";
 44
 45      public PgSQLConnectionImpl(
 46         PostgreSQLProtocol functionality,
 47         DatabaseMetadata metaData
 48         )
 49         : base( functionality, metaData )
 50      {
 51      }
 52
 53      //public event GenericEventHandler<NotificationEventArgs> NotificationEvent;
 54
 55      public Int32 BackendProcessID => this.ConnectionFunctionality.BackendProcessID;
 56
 57      public TransactionStatus LastSeenTransactionStatus => this.ConnectionFunctionality.LastSeenTransactionStatus;
 58
 59      public override ValueTask<Boolean> ProcessStatementResultPassively(
 60         MemorizingPotentiallyAsyncReader<Char?, Char> reader,
 61         SQLStatementBuilderInformation statementInformation,
 62         SQLStatementExecutionResult executionResult
 63         )
 64      {
 65         // TODO detect COPY IN result from executionResult, and use reader to read data and send it to backend
 66         return new ValueTask<Boolean>( false );
 67      }
 68
 69      public ValueTask<NotificationEventArgs[]> CheckNotificationsAsync()
 70      {
 71         return this.ConnectionFunctionality.CheckNotificationsAsync();
 72
 73         //         if ( !argsArray.IsNullOrEmpty() )
 74         //         {
 75         //            foreach ( var args in argsArray )
 76         //            {
 77         //#if DEBUG
 78         //               this.NotificationEvent?.Invoke( args );
 79         //#else
 80         //                     var curArgs = args;
 81         //                     this.NotificationEvent?.InvokeAllEventHandlers( evt => evt( curArgs ), throwExceptions: 
 82         //#endif
 83         //            }
 84         //         }
 85
 86         //         return argsArray?.Length ?? 0;
 87      }
 88
 89      public IAsyncEnumerable<NotificationEventArgs> ContinuouslyListenToNotificationsAsync()
 90      {
 91         return this.ConnectionFunctionality.ListenToNotificationsAsync();
 92      }
 93
 94      public TypeRegistry TypeRegistry => this.ConnectionFunctionality.TypeRegistry;
 95
 96      protected override String GetSQLForGettingReadOnly()
 97      {
 98         return "SHOW default_transaction_read_only";
 99      }
 100
 101      protected override String GetSQLForGettingTransactionIsolationLevel()
 102      {
 103         return "SHOW TRANSACTION ISOLATION LEVEL";
 104      }
 105
 106      protected override String GetSQLForSettingReadOnly( Boolean isReadOnly )
 107      {
 108         this.ThrowIfInTransaction( "read-only property" );
 109         return "SET SESSION CHARACTERISTICS AS TRANSACTION READ " + ( isReadOnly ? "ONLY" : "WRITE" );
 110      }
 111
 112      protected override String GetSQLForSettingTransactionIsolationLevel( TransactionIsolationLevel level )
 113      {
 114         this.ThrowIfInTransaction( "default transaction isolation level" );
 115         String levelString;
 116         switch ( level )
 117         {
 118            case TransactionIsolationLevel.ReadUncommitted:
 119               levelString = READ_UNCOMMITTED;
 120               break;
 121            case TransactionIsolationLevel.ReadCommitted:
 122               levelString = READ_COMMITTED;
 123               break;
 124            case TransactionIsolationLevel.RepeatableRead:
 125               levelString = REPEATABLE_READ;
 126               break;
 127            case TransactionIsolationLevel.Serializable:
 128               levelString = SERIALIZABLE;
 129               break;
 130            default:
 131               throw new ArgumentException( "Unsupported isolation level: " + level + "." );
 132         }
 133         return levelString;
 134      }
 135
 136      protected override async ValueTask<Boolean> InterpretReadOnly( AsyncDataColumn row )
 137      {
 138         return String.Equals( (String) ( await row.TryGetValueAsync() ).Result, "on", StringComparison.OrdinalIgnoreCas
 139      }
 140
 141      protected override async ValueTask<TransactionIsolationLevel> InterpretTransactionIsolationLevel( AsyncDataColumn 
 142      {
 143         TransactionIsolationLevel retVal;
 144         String levelString;
 145         switch ( ( levelString = (String) ( await row.TryGetValueAsync() ).Result ) )
 146         {
 147            case READ_UNCOMMITTED:
 148               retVal = TransactionIsolationLevel.ReadUncommitted;
 149               break;
 150            case READ_COMMITTED:
 151               retVal = TransactionIsolationLevel.ReadCommitted;
 152               break;
 153            case REPEATABLE_READ:
 154               retVal = TransactionIsolationLevel.RepeatableRead;
 155               break;
 156            case SERIALIZABLE:
 157               retVal = TransactionIsolationLevel.Serializable;
 158               break;
 159            default:
 160               throw new ArgumentException( $"Unrecognied transaction isolation level from backend: \"{levelString}\"." 
 161         }
 162         return retVal;
 163      }
 164
 165      private void ThrowIfInTransaction( String what )
 166      {
 167         if ( this.ConnectionFunctionality.LastSeenTransactionStatus != TransactionStatus.Idle )
 168         {
 169            throw new NotSupportedException( "Can not change " + what + " while in middle of transaction." );
 170         }
 171      }
 172
 173
 174   }
 175
 176   internal sealed class PgSQLConnectionVendorFunctionalityImpl : DefaultConnectionVendorFunctionality, PgSQLConnectionV
 177   {
 24178      public PgSQLConnectionVendorFunctionalityImpl()
 179      {
 24180         this.StandardConformingStrings = true;
 24181      }
 182
 183      public override String EscapeLiteral( String str )
 184      {
 29185         if ( !String.IsNullOrEmpty( str ) )
 186         {
 29187            if ( this.StandardConformingStrings )
 188            {
 189               const String STANDARD_ESCAPABLE = "'";
 190               const String STANDARD_REPLACEABLE = "''";
 29191               if ( str.IndexOf( STANDARD_ESCAPABLE ) >= 0 )
 192               {
 0193                  str = str.Replace( STANDARD_ESCAPABLE, STANDARD_REPLACEABLE );
 194               }
 0195            }
 196            else
 197            {
 198               // Escape both backslashes and single-quotes by doubling
 199               //
 0200               if ( str.IndexOfAny( new[] { '\'', '\\' } ) >= 0 )
 201               {
 202                  // Use Regex for now but consider doing manual replacing if performance becomes a problem
 203                  // C# does not allow replacing any character in string with other as built-in function, so just do thi
 0204                  var sb = new StringBuilder( str.Length + 5 );
 0205                  foreach ( var ch in str )
 206                  {
 0207                     sb.Append( ch );
 0208                     if ( ch == '\'' || ch == '\\' )
 209                     {
 0210                        sb.Append( ch );
 211                     }
 212                  }
 0213                  str = sb.ToString();
 214               }
 215            }
 216         }
 217
 29218         return str;
 219      }
 220
 221      protected override SQLStatementBuilder CreateStatementBuilder( String sql, Int32[] parameterIndices )
 222      {
 60223         var paramz = new StatementParameter[parameterIndices?.Length ?? 0];
 60224         var batchParams = new List<StatementParameter[]>();
 60225         var info = new PgSQLStatementBuilderInformation( sql, paramz, batchParams, parameterIndices );
 60226         return new PgSQLStatementBuilder( info, paramz, batchParams );
 227      }
 228
 229      protected override Boolean TryParseStatementSQL( String sql, out Int32[] parameterIndices )
 230      {
 231         // We accept either:
 232         // 1. Multiple simple statements, OR
 233         // 2. Exactly one statement with parameters
 60234         parameterIndices = null;
 60235         var strIdx = new StringIndex( sql );
 60236         var boundReader = ReaderFactory.NewNullablePeekableValueReader(
 60237            StringCharacterReaderLogic.Instance,
 60238            strIdx
 60239            );
 240         Boolean wasOK;
 241         do
 242         {
 243            // Because how ValueTask works, and since StringCharacterReader never performs any asynchrony, we will alway
 62244            var curParameterIndices = Parser.ParseStringForNextSQLStatement(
 62245               boundReader,
 62246               this.StandardConformingStrings,
 76247               () => strIdx.CurrentIndex - 1
 62248               ).GetAwaiter().GetResult();
 62249            wasOK = curParameterIndices == null || parameterIndices == null;
 62250            parameterIndices = curParameterIndices;
 62251         } while ( wasOK && strIdx.CurrentIndex < sql.Length );
 252
 60253         return wasOK;
 254
 255      }
 256
 257      public override async ValueTask<Boolean> TryAdvanceReaderOverSingleStatement( PeekablePotentiallyAsyncReader<Char?
 258      {
 0259         await Parser.ParseStringForNextSQLStatement( reader, this.StandardConformingStrings, null );
 0260         return true;
 0261      }
 262
 263      public ValueTask<Boolean> TryAdvanceReaderOverCopyInStatement( PeekablePotentiallyAsyncReader<Char?> reader )
 264      {
 265         // TODO
 0266         return new ValueTask<Boolean>( false );
 267      }
 268
 269      // TODO: allow this to reflect the configurable propery of backend.
 115270      public Boolean StandardConformingStrings { get; set; }
 271
 272      //private static Boolean AllSpaces( String sql, Int32 startIdxInclusive )
 273      //{
 274      //   var retVal = true;
 275      //   for ( var i = startIdxInclusive; i < sql.Length && retVal; ++i )
 276      //   {
 277      //      if ( !Parser.IsSpace( sql[i] ) )
 278      //      {
 279      //         retVal = false;
 280      //      }
 281      //   }
 282
 283      //   return retVal;
 284      //}
 285
 286      //private static void FindCopyInEndFromTextReader( TextReader reader, ref Char[] auxArray )
 287      //{
 288      //   const Int32 AUX_ARRAY_LEN = 3;
 289      //   if ( auxArray == null )
 290      //   {
 291      //      // We need 3 characters to detect the end of COPY IN FROM STDIN statement (line-break, \.)
 292      //      // The following line-break can be validated by using .Peek();
 293      //      auxArray = new Char[AUX_ARRAY_LEN];
 294      //   }
 295      //   Int32 c;
 296      //   var arrayIndex = 0;
 297      //   var found = false;
 298      //   while ( !found && ( c = reader.Read() ) != -1 )
 299      //   {
 300      //      // Update auxiliary array
 301      //      auxArray[arrayIndex] = (Char) c;
 302
 303      //      if ( CSDBC.Core.PostgreSQL.Implementation.Parser.CheckForCircularlyFilledArray(
 304      //         COPY_IN_END_CHARS,
 305      //         auxArray,
 306      //         arrayIndex
 307      //         ) )
 308      //      {
 309      //         // We've found '\.', now we need to check for line-ends before and after
 310      //         var peek = reader.Peek();
 311      //         if ( peek == '\n' || peek == '\r' )
 312      //         {
 313      //            // This '\.' is followed by new-line. Now check that it is also preceded by a new-line
 314      //            var ch = auxArray[( arrayIndex + 1 ) % AUX_ARRAY_LEN];
 315      //            found = IsNewline( ch );
 316      //            if ( found )
 317      //            {
 318      //               // Consume the linebreak
 319      //               reader.Read();
 320      //            }
 321      //         }
 322      //      }
 323
 324      //      if ( arrayIndex == AUX_ARRAY_LEN - 1 )
 325      //      {
 326      //         arrayIndex = 0;
 327      //      }
 328      //      else
 329      //      {
 330      //         ++arrayIndex;
 331      //      }
 332      //   }
 333      //}
 334
 335      //private static Boolean IsNewline( Char ch )
 336      //{
 337      //   return ch == '\n' || ch == '\r';
 338      //}
 339
 340   }
 341
 342}