storage/sql: use isolation level "serializable" for transactions
This commit is contained in:
		| @@ -36,6 +36,7 @@ func cleanDB(c *conn) error { | |||||||
| 		delete from auth_code; | 		delete from auth_code; | ||||||
| 		delete from refresh_token; | 		delete from refresh_token; | ||||||
| 		delete from keys; | 		delete from keys; | ||||||
|  | 		delete from password; | ||||||
| 	`) | 	`) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| @@ -48,6 +49,7 @@ func TestSQLite3(t *testing.T) { | |||||||
| 		s := &SQLite3{":memory:"} | 		s := &SQLite3{":memory:"} | ||||||
| 		conn, err := s.open() | 		conn, err := s.open() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | 			fmt.Fprintln(os.Stdout, err) | ||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 		return conn | 		return conn | ||||||
| @@ -58,15 +60,25 @@ func TestSQLite3(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func getenv(key, defaultVal string) string { | ||||||
|  | 	if val := os.Getenv(key); val != "" { | ||||||
|  | 		return val | ||||||
|  | 	} | ||||||
|  | 	return defaultVal | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const testPostgresEnv = "DEX_POSTGRES_HOST" | ||||||
|  |  | ||||||
| func TestPostgres(t *testing.T) { | func TestPostgres(t *testing.T) { | ||||||
| 	if os.Getenv("DEX_POSTGRES_HOST") == "" { | 	host := os.Getenv(testPostgresEnv) | ||||||
| 		t.Skip("postgres envs not set, skipping tests") | 	if host == "" { | ||||||
|  | 		t.Skipf("test environment variable %q not set, skipping", testPostgresEnv) | ||||||
| 	} | 	} | ||||||
| 	p := Postgres{ | 	p := Postgres{ | ||||||
| 		Database: os.Getenv("DEX_POSTGRES_DATABASE"), | 		Database: getenv("DEX_POSTGRES_DATABASE", "postgres"), | ||||||
| 		User:     os.Getenv("DEX_POSTGRES_USER"), | 		User:     getenv("DEX_POSTGRES_USER", "postgres"), | ||||||
| 		Password: os.Getenv("DEX_POSTGRES_PASSWORD"), | 		Password: getenv("DEX_POSTGRES_PASSWORD", "postgres"), | ||||||
| 		Host:     os.Getenv("DEX_POSTGRES_HOST"), | 		Host:     host, | ||||||
| 		SSL: PostgresSSL{ | 		SSL: PostgresSSL{ | ||||||
| 			Mode: sslDisable, // Postgres container doesn't support SSL. | 			Mode: sslDisable, // Postgres container doesn't support SSL. | ||||||
| 		}, | 		}, | ||||||
| @@ -92,4 +104,7 @@ func TestPostgres(t *testing.T) { | |||||||
| 	withTimeout(time.Minute*1, func() { | 	withTimeout(time.Minute*1, func() { | ||||||
| 		conformance.RunTests(t, newStorage) | 		conformance.RunTests(t, newStorage) | ||||||
| 	}) | 	}) | ||||||
|  | 	withTimeout(time.Minute*1, func() { | ||||||
|  | 		conformance.RunTransactionTests(t, newStorage) | ||||||
|  | 	}) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -45,7 +45,30 @@ func matchLiteral(s string) *regexp.Regexp { | |||||||
| var ( | var ( | ||||||
| 	// The "github.com/lib/pq" driver is the default flavor. All others are | 	// The "github.com/lib/pq" driver is the default flavor. All others are | ||||||
| 	// translations of this. | 	// translations of this. | ||||||
| 	flavorPostgres = flavor{} | 	flavorPostgres = flavor{ | ||||||
|  | 		// The default behavior for Postgres transactions is consistent reads, not consistent writes. | ||||||
|  | 		// For each transaction opened, ensure it has the correct isolation level. | ||||||
|  | 		// | ||||||
|  | 		// See: https://www.postgresql.org/docs/9.3/static/sql-set-transaction.html | ||||||
|  | 		// | ||||||
|  | 		// NOTE(ericchiang): For some reason using `SET SESSION CHARACTERISTICS AS TRANSACTION` at a | ||||||
|  | 		// session level didn't work for some edge cases. Might be something worth exploring. | ||||||
|  | 		executeTx: func(db *sql.DB, fn func(sqlTx *sql.Tx) error) error { | ||||||
|  | 			tx, err := db.Begin() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			defer tx.Rollback() | ||||||
|  |  | ||||||
|  | 			if _, err := tx.Exec(`SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;`); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			if err := fn(tx); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			return tx.Commit() | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	flavorSQLite3 = flavor{ | 	flavorSQLite3 = flavor{ | ||||||
| 		queryReplacers: []replacer{ | 		queryReplacers: []replacer{ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user