Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions .github/workflows/ado-net-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
on:
push:
branches: [ main ]
pull_request:
permissions:
contents: read
pull-requests: write
name: ADO.NET Tests
jobs:
units:
strategy:
matrix:
dotnet-version: ['8.0.x', '9.0.x']
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '1.25'
# Install compilers for cross-compiling between operating systems.
- name: Install compilers
run: |
echo "$RUNNER_OS"
if [ "$RUNNER_OS" == "Windows" ]; then
echo "Windows does not yet support cross compiling"
elif [ "$RUNNER_OS" == "macOS" ]; then
brew tap SergioBenitez/osxct
brew install x86_64-unknown-linux-gnu
brew install mingw-w64
else
sudo apt-get update
sudo apt install -y g++-mingw-w64-x86-64 gcc-mingw-w64-x86-64
sudo apt-get install -y gcc-arm-linux-gnueabihf
fi
shell: bash
- name: Build the .NET wrapper
working-directory: spannerlib/wrappers/spannerlib-dotnet
run: |
echo "$RUNNER_OS"
./build.sh
shell: bash
- name: Restore .NET wrapper dependencies
run: dotnet restore
working-directory: spannerlib/wrappers/spannerlib-dotnet
shell: bash
- name: Build .NET wrapper
run: dotnet build --no-restore -c Release
working-directory: spannerlib/wrappers/spannerlib-dotnet
shell: bash
- name: spanner-ado-net-tests
working-directory: drivers/spanner-ado-net/spanner-ado-net-tests
run: dotnet test --verbosity normal
shell: bash
- name: spanner-ado-net-specification-tests
working-directory: drivers/spanner-ado-net/spanner-ado-net-specification-tests
run: dotnet test --verbosity normal
shell: bash
44 changes: 44 additions & 0 deletions conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@ type SpannerConn interface {
// DetectStatementType returns the type of SQL statement.
DetectStatementType(query string) parser.StatementType

// Parser returns the parser.StatementParser that is used for this connection.
Parser() *parser.StatementParser

// resetTransactionForRetry resets the current transaction after it has
// been aborted by Spanner. Calling this function on a transaction that
// has not been aborted is not supported and will cause an error to be
Expand Down Expand Up @@ -265,6 +268,7 @@ type conn struct {
tx *delegatingTransaction
prevTx *delegatingTransaction
resetForRetry bool
instance string
database string

execSingleQuery func(ctx context.Context, c *spanner.Client, statement spanner.Statement, statementInfo *parser.StatementInfo, bound spanner.TimestampBound, options *ExecOptions) *spanner.RowIterator
Expand Down Expand Up @@ -294,6 +298,10 @@ func (c *conn) DetectStatementType(query string) parser.StatementType {
return info.StatementType
}

func (c *conn) Parser() *parser.StatementParser {
return c.parser
}

func (c *conn) CommitTimestamp() (time.Time, error) {
ts := propertyCommitTimestamp.GetValueOrDefault(c.state)
if ts == nil {
Expand Down Expand Up @@ -420,6 +428,16 @@ func (c *conn) setReadOnlyStaleness(staleness spanner.TimestampBound) (driver.Re
return driver.ResultNoRows, nil
}

func (c *conn) readOnlyStalenessPointer() *spanner.TimestampBound {
val := propertyReadOnlyStaleness.GetConnectionPropertyValue(c.state)
if val == nil || !val.HasValue() {
return nil
}
staleness, _ := val.GetValue()
timestampBound := staleness.(spanner.TimestampBound)
return &timestampBound
}

func (c *conn) IsolationLevel() sql.IsolationLevel {
return propertyIsolationLevel.GetValueOrDefault(c.state)
}
Expand Down Expand Up @@ -650,6 +668,28 @@ func (c *conn) execDDL(ctx context.Context, statements ...spanner.Statement) (dr
for i, s := range statements {
ddlStatements[i] = s.SQL
}
if c.parser.IsCreateDatabaseStatement(ddlStatements[0]) {
op, err := c.adminClient.CreateDatabase(ctx, &adminpb.CreateDatabaseRequest{
CreateStatement: ddlStatements[0],
DatabaseDialect: c.parser.Dialect,
Parent: c.instance,
ExtraStatements: ddlStatements[1:],
})
if err != nil {
return nil, err
}
if _, err := op.Wait(ctx); err != nil {
return nil, err
}
return driver.ResultNoRows, nil
} else if c.parser.IsDropDatabaseStatement(ddlStatements[0]) {
stmt := &parser.ParsedDropDatabaseStatement{}
if err := stmt.Parse(c.parser, ddlStatements[0]); err != nil {
return nil, err
}
return (&executableDropDatabaseStatement{stmt}).execContext(ctx, c, nil)
}

op, err := c.adminClient.UpdateDatabaseDdl(ctx, &adminpb.UpdateDatabaseDdlRequest{
Database: c.database,
Statements: ddlStatements,
Expand Down Expand Up @@ -1205,6 +1245,7 @@ func (c *conn) options(reset bool) (*ExecOptions, error) {
},
},
PartitionedQueryOptions: PartitionedQueryOptions{},
TimestampBound: c.readOnlyStalenessPointer(),
}
if c.tempExecOptions != nil {
effectiveOptions.merge(c.tempExecOptions)
Expand Down Expand Up @@ -1585,6 +1626,9 @@ func (c *conn) Rollback(ctx context.Context) error {
}

func queryInSingleUse(ctx context.Context, c *spanner.Client, statement spanner.Statement, statementInfo *parser.StatementInfo, tb spanner.TimestampBound, options *ExecOptions) *spanner.RowIterator {
if options.TimestampBound != nil {
tb = *options.TimestampBound
}
return c.Single().WithTimestampBound(tb).QueryWithOptions(ctx, statement, options.QueryOptions)
}

Expand Down
28 changes: 28 additions & 0 deletions conn_with_mockserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,34 @@ func TestSetLocalReadLockMode(t *testing.T) {
}
}

func TestTimestampBound(t *testing.T) {
t.Parallel()

db, server, teardown := setupTestDBConnection(t)
defer teardown()
ctx := context.Background()

staleness := spanner.MaxStaleness(10 * time.Second)
row := db.QueryRowContext(ctx, testutil.SelectFooFromBar, ExecOptions{TimestampBound: &staleness})
if row.Err() != nil {
t.Fatal(row.Err())
}
var val int64
if err := row.Scan(&val); err != nil {
t.Fatal(err)
}

requests := server.TestSpanner.DrainRequestsFromServer()
executeRequests := testutil.RequestsOfType(requests, reflect.TypeOf(&spannerpb.ExecuteSqlRequest{}))
if g, w := len(executeRequests), 1; g != w {
t.Fatalf("execute requests count mismatch\n Got: %v\nWant: %v", g, w)
}
request := executeRequests[0].(*spannerpb.ExecuteSqlRequest)
if g, w := request.Transaction.GetSingleUse().GetReadOnly().GetMaxStaleness().GetSeconds(), int64(10); g != w {
t.Fatalf("read staleness mismatch\n Got: %v\nWant: %v", g, w)
}
}

func TestCreateDatabase(t *testing.T) {
t.Parallel()

Expand Down
20 changes: 20 additions & 0 deletions driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ type ExecOptions struct {
TransactionOptions spanner.TransactionOptions
// QueryOptions are the query options that will be used for the statement.
QueryOptions spanner.QueryOptions
// TimestampBound is the timestamp bound that will be used for the statement
// if it is a query outside a transaction. Setting this option will override
// the default TimestampBound that is set on the connection.
TimestampBound *spanner.TimestampBound

// PartitionedQueryOptions are used for partitioned queries, and ignored
// for all other statements.
Expand Down Expand Up @@ -246,6 +250,9 @@ func (dest *ExecOptions) merge(src *ExecOptions) {
if src.AutocommitDMLMode != Unspecified {
dest.AutocommitDMLMode = src.AutocommitDMLMode
}
if src.TimestampBound != nil {
dest.TimestampBound = src.TimestampBound
}
if src.PropertyValues != nil {
dest.PropertyValues = append(dest.PropertyValues, src.PropertyValues...)
}
Expand Down Expand Up @@ -742,6 +749,10 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
func openDriverConn(ctx context.Context, c *connector) (driver.Conn, error) {
opts := c.options
c.logger.Log(ctx, LevelNotice, "opening connection")
instanceName := fmt.Sprintf(
"projects/%s/instances/%s",
c.connectorConfig.Project,
c.connectorConfig.Instance)
databaseName := fmt.Sprintf(
"projects/%s/instances/%s/databases/%s",
c.connectorConfig.Project,
Expand Down Expand Up @@ -780,6 +791,7 @@ func openDriverConn(ctx context.Context, c *connector) (driver.Conn, error) {
adminClient: c.adminClient,
connId: connId,
logger: logger,
instance: instanceName,
database: databaseName,
state: createInitialConnectionState(connectionStateType, c.initialPropertyValues),
execSingleQuery: queryInSingleUse,
Expand Down Expand Up @@ -1527,12 +1539,20 @@ func parseIsolationLevel(val string) (sql.IsolationLevel, error) {
return sql.LevelDefault, nil
case "read_uncommitted":
return sql.LevelReadUncommitted, nil
case "readuncommitted":
return sql.LevelReadUncommitted, nil
case "read_committed":
return sql.LevelReadCommitted, nil
case "readcommitted":
return sql.LevelReadCommitted, nil
case "write_committed":
return sql.LevelWriteCommitted, nil
case "writecommitted":
return sql.LevelWriteCommitted, nil
case "repeatable_read":
return sql.LevelRepeatableRead, nil
case "repeatableread":
return sql.LevelRepeatableRead, nil
case "snapshot":
return sql.LevelSnapshot, nil
case "serializable":
Expand Down
4 changes: 4 additions & 0 deletions drivers/spanner-ado-net/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.idea
obj
bin
*DotSettings.user
Loading
Loading