Repository: sqlc-dev/sqlc
Pull request #2744 (2023-09-20 00:14:32)

test(endtoend): Verify all schemas in endtoend
For each test in the endtoend package, use the new sqlc managed databases to verify that the schemas are valid. In the future, we'll attempt to prepare all the queries as well.


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3706e74b6..b9417c0ae 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -55,6 +55,8 @@ jobs:
       with:
         go-version: '1.21.1'
 
+    - run: curl -I https://debug.fly.dev
+
     - name: install gotestsum
       run: go install gotest.tools/gotestsum@latest
 
@@ -76,6 +78,8 @@ jobs:
         MYSQL_HOST: localhost
         MYSQL_PORT: ${{ job.services.mysql.ports['3306'] }}
         MYSQL_ROOT_PASSWORD: mysecretpassword
+        DDL_SQLC_PROJECT_ID: ${{ secrets.DDL_SQLC_PROJECT_ID }}
+        DDL_SQLC_AUTH_TOKEN: ${{ secrets.DDL_SQLC_AUTH_TOKEN }}
 
     - name: build internal/endtoend
       run: go build ./...
diff --git a/internal/quickdb/rpc.go b/internal/quickdb/rpc.go
new file mode 100644
index 000000000..c1f9ffd4c
--- /dev/null
+++ b/internal/quickdb/rpc.go
@@ -0,0 +1,57 @@
+package quickdb
+
+import (
+	"crypto/tls"
+	"os"
+
+	"github.com/riza-io/grpc-go/credentials/basic"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
+
+	"github.com/sqlc-dev/sqlc/internal/config"
+	pb "github.com/sqlc-dev/sqlc/internal/quickdb/v1"
+)
+
+const defaultHostname = "grpc.sqlc.dev"
+
+func NewClientFromConfig(cloudConfig config.Cloud) (pb.QuickClient, error) {
+	projectID := cloudConfig.Project
+	authToken := os.Getenv("SQLC_AUTH_TOKEN")
+	return NewClient(projectID, authToken, WithHost(cloudConfig.Hostname))
+}
+
+type options struct {
+	hostname string
+}
+
+type Option func(*options)
+
+func WithHost(host string) Option {
+	return func(o *options) {
+		o.hostname = host
+	}
+}
+
+func NewClient(project, token string, opts ...Option) (pb.QuickClient, error) {
+	var o options
+	for _, apply := range opts {
+		apply(&o)
+	}
+
+	dialOpts := []grpc.DialOption{
+		grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})),
+		grpc.WithPerRPCCredentials(basic.NewPerRPCCredentials(project, token)),
+	}
+
+	hostname := o.hostname
+	if hostname == "" {
+		hostname = defaultHostname
+	}
+
+	conn, err := grpc.Dial(hostname+":443", dialOpts...)
+	if err != nil {
+		return nil, err
+	}
+
+	return pb.NewQuickClient(conn), nil
+}
diff --git a/internal/quickdb/v1/quickdb.pb.go b/internal/quickdb/v1/quickdb.pb.go
new file mode 100755
index 000000000..7bdec9974
--- /dev/null
+++ b/internal/quickdb/v1/quickdb.pb.go
@@ -0,0 +1,507 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.30.0
+// 	protoc        v3.21.7
+// source: proto/quickdb/v1/quickdb.proto
+
+package v1
+
+import (
+	context "context"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type CreateEphemeralDatabaseRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Region     string   `protobuf:"bytes,1,opt,name=region,proto3" json:"region,omitempty"`
+	Engine     string   `protobuf:"bytes,2,opt,name=engine,proto3" json:"engine,omitempty"`
+	ServerId   string   `protobuf:"bytes,3,opt,name=server_id,json=serverId,proto3" json:"server_id,omitempty"`
+	Migrations []string `protobuf:"bytes,4,rep,name=migrations,proto3" json:"migrations,omitempty"`
+}
+
+func (x *CreateEphemeralDatabaseRequest) Reset() {
+	*x = CreateEphemeralDatabaseRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proto_quickdb_v1_quickdb_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CreateEphemeralDatabaseRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateEphemeralDatabaseRequest) ProtoMessage() {}
+
+func (x *CreateEphemeralDatabaseRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_proto_quickdb_v1_quickdb_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CreateEphemeralDatabaseRequest.ProtoReflect.Descriptor instead.
+func (*CreateEphemeralDatabaseRequest) Descriptor() ([]byte, []int) {
+	return file_proto_quickdb_v1_quickdb_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *CreateEphemeralDatabaseRequest) GetRegion() string {
+	if x != nil {
+		return x.Region
+	}
+	return ""
+}
+
+func (x *CreateEphemeralDatabaseRequest) GetEngine() string {
+	if x != nil {
+		return x.Engine
+	}
+	return ""
+}
+
+func (x *CreateEphemeralDatabaseRequest) GetServerId() string {
+	if x != nil {
+		return x.ServerId
+	}
+	return ""
+}
+
+func (x *CreateEphemeralDatabaseRequest) GetMigrations() []string {
+	if x != nil {
+		return x.Migrations
+	}
+	return nil
+}
+
+type CreateEphemeralDatabaseResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	DatabaseId string `protobuf:"bytes,1,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"`
+	Uri        string `protobuf:"bytes,2,opt,name=uri,proto3" json:"uri,omitempty"`
+}
+
+func (x *CreateEphemeralDatabaseResponse) Reset() {
+	*x = CreateEphemeralDatabaseResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proto_quickdb_v1_quickdb_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CreateEphemeralDatabaseResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateEphemeralDatabaseResponse) ProtoMessage() {}
+
+func (x *CreateEphemeralDatabaseResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_proto_quickdb_v1_quickdb_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CreateEphemeralDatabaseResponse.ProtoReflect.Descriptor instead.
+func (*CreateEphemeralDatabaseResponse) Descriptor() ([]byte, []int) {
+	return file_proto_quickdb_v1_quickdb_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *CreateEphemeralDatabaseResponse) GetDatabaseId() string {
+	if x != nil {
+		return x.DatabaseId
+	}
+	return ""
+}
+
+func (x *CreateEphemeralDatabaseResponse) GetUri() string {
+	if x != nil {
+		return x.Uri
+	}
+	return ""
+}
+
+type DropEphemeralDatabaseRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	DatabaseId string `protobuf:"bytes,1,opt,name=database_id,json=databaseId,proto3" json:"database_id,omitempty"`
+}
+
+func (x *DropEphemeralDatabaseRequest) Reset() {
+	*x = DropEphemeralDatabaseRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proto_quickdb_v1_quickdb_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *DropEphemeralDatabaseRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DropEphemeralDatabaseRequest) ProtoMessage() {}
+
+func (x *DropEphemeralDatabaseRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_proto_quickdb_v1_quickdb_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use DropEphemeralDatabaseRequest.ProtoReflect.Descriptor instead.
+func (*DropEphemeralDatabaseRequest) Descriptor() ([]byte, []int) {
+	return file_proto_quickdb_v1_quickdb_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *DropEphemeralDatabaseRequest) GetDatabaseId() string {
+	if x != nil {
+		return x.DatabaseId
+	}
+	return ""
+}
+
+type DropEphemeralDatabaseResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *DropEphemeralDatabaseResponse) Reset() {
+	*x = DropEphemeralDatabaseResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proto_quickdb_v1_quickdb_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *DropEphemeralDatabaseResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DropEphemeralDatabaseResponse) ProtoMessage() {}
+
+func (x *DropEphemeralDatabaseResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_proto_quickdb_v1_quickdb_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use DropEphemeralDatabaseResponse.ProtoReflect.Descriptor instead.
+func (*DropEphemeralDatabaseResponse) Descriptor() ([]byte, []int) {
+	return file_proto_quickdb_v1_quickdb_proto_rawDescGZIP(), []int{3}
+}
+
+var File_proto_quickdb_v1_quickdb_proto protoreflect.FileDescriptor
+
+var file_proto_quickdb_v1_quickdb_proto_rawDesc = []byte{
+	0x0a, 0x1e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x71, 0x75, 0x69, 0x63, 0x6b, 0x64, 0x62, 0x2f,
+	0x76, 0x31, 0x2f, 0x71, 0x75, 0x69, 0x63, 0x6b, 0x64, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x12, 0x1a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x73, 0x71, 0x6c, 0x63, 0x2e, 0x64, 0x65,
+	0x76, 0x2e, 0x71, 0x75, 0x69, 0x63, 0x6b, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x22, 0x8d, 0x01, 0x0a,
+	0x1e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c,
+	0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+	0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x67, 0x69, 0x6e,
+	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x12,
+	0x1b, 0x0a, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a,
+	0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09,
+	0x52, 0x0a, 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x54, 0x0a, 0x1f,
+	0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x44,
+	0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
+	0x1f, 0x0a, 0x0b, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x49, 0x64,
+	0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75,
+	0x72, 0x69, 0x22, 0x3f, 0x0a, 0x1c, 0x44, 0x72, 0x6f, 0x70, 0x45, 0x70, 0x68, 0x65, 0x6d, 0x65,
+	0x72, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x69,
+	0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73,
+	0x65, 0x49, 0x64, 0x22, 0x1f, 0x0a, 0x1d, 0x44, 0x72, 0x6f, 0x70, 0x45, 0x70, 0x68, 0x65, 0x6d,
+	0x65, 0x72, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70,
+	0x6f, 0x6e, 0x73, 0x65, 0x32, 0xab, 0x02, 0x0a, 0x05, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x12, 0x92,
+	0x01, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72,
+	0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x3a, 0x2e, 0x72, 0x65, 0x6d,
+	0x6f, 0x74, 0x65, 0x2e, 0x73, 0x71, 0x6c, 0x63, 0x2e, 0x64, 0x65, 0x76, 0x2e, 0x71, 0x75, 0x69,
+	0x63, 0x6b, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x70,
+	0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e,
+	0x73, 0x71, 0x6c, 0x63, 0x2e, 0x64, 0x65, 0x76, 0x2e, 0x71, 0x75, 0x69, 0x63, 0x6b, 0x64, 0x62,
+	0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x70, 0x68, 0x65, 0x6d, 0x65,
+	0x72, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
+	0x6e, 0x73, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x15, 0x44, 0x72, 0x6f, 0x70, 0x45, 0x70, 0x68, 0x65,
+	0x6d, 0x65, 0x72, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x38, 0x2e,
+	0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x73, 0x71, 0x6c, 0x63, 0x2e, 0x64, 0x65, 0x76, 0x2e,
+	0x71, 0x75, 0x69, 0x63, 0x6b, 0x64, 0x62, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x72, 0x6f, 0x70, 0x45,
+	0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65,
+	0x2e, 0x73, 0x71, 0x6c, 0x63, 0x2e, 0x64, 0x65, 0x76, 0x2e, 0x71, 0x75, 0x69, 0x63, 0x6b, 0x64,
+	0x62, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x72, 0x6f, 0x70, 0x45, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72,
+	0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+	0x73, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_proto_quickdb_v1_quickdb_proto_rawDescOnce sync.Once
+	file_proto_quickdb_v1_quickdb_proto_rawDescData = file_proto_quickdb_v1_quickdb_proto_rawDesc
+)
+
+func file_proto_quickdb_v1_quickdb_proto_rawDescGZIP() []byte {
+	file_proto_quickdb_v1_quickdb_proto_rawDescOnce.Do(func() {
+		file_proto_quickdb_v1_quickdb_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_quickdb_v1_quickdb_proto_rawDescData)
+	})
+	return file_proto_quickdb_v1_quickdb_proto_rawDescData
+}
+
+var file_proto_quickdb_v1_quickdb_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
+var file_proto_quickdb_v1_quickdb_proto_goTypes = []interface{}{
+	(*CreateEphemeralDatabaseRequest)(nil),  // 0: remote.sqlc.dev.quickdb.v1.CreateEphemeralDatabaseRequest
+	(*CreateEphemeralDatabaseResponse)(nil), // 1: remote.sqlc.dev.quickdb.v1.CreateEphemeralDatabaseResponse
+	(*DropEphemeralDatabaseRequest)(nil),    // 2: remote.sqlc.dev.quickdb.v1.DropEphemeralDatabaseRequest
+	(*DropEphemeralDatabaseResponse)(nil),   // 3: remote.sqlc.dev.quickdb.v1.DropEphemeralDatabaseResponse
+}
+var file_proto_quickdb_v1_quickdb_proto_depIdxs = []int32{
+	0, // 0: remote.sqlc.dev.quickdb.v1.Quick.CreateEphemeralDatabase:input_type -> remote.sqlc.dev.quickdb.v1.CreateEphemeralDatabaseRequest
+	2, // 1: remote.sqlc.dev.quickdb.v1.Quick.DropEphemeralDatabase:input_type -> remote.sqlc.dev.quickdb.v1.DropEphemeralDatabaseRequest
+	1, // 2: remote.sqlc.dev.quickdb.v1.Quick.CreateEphemeralDatabase:output_type -> remote.sqlc.dev.quickdb.v1.CreateEphemeralDatabaseResponse
+	3, // 3: remote.sqlc.dev.quickdb.v1.Quick.DropEphemeralDatabase:output_type -> remote.sqlc.dev.quickdb.v1.DropEphemeralDatabaseResponse
+	2, // [2:4] is the sub-list for method output_type
+	0, // [0:2] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_proto_quickdb_v1_quickdb_proto_init() }
+func file_proto_quickdb_v1_quickdb_proto_init() {
+	if File_proto_quickdb_v1_quickdb_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_proto_quickdb_v1_quickdb_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CreateEphemeralDatabaseRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_proto_quickdb_v1_quickdb_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CreateEphemeralDatabaseResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_proto_quickdb_v1_quickdb_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*DropEphemeralDatabaseRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_proto_quickdb_v1_quickdb_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*DropEphemeralDatabaseResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_proto_quickdb_v1_quickdb_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   4,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_proto_quickdb_v1_quickdb_proto_goTypes,
+		DependencyIndexes: file_proto_quickdb_v1_quickdb_proto_depIdxs,
+		MessageInfos:      file_proto_quickdb_v1_quickdb_proto_msgTypes,
+	}.Build()
+	File_proto_quickdb_v1_quickdb_proto = out.File
+	file_proto_quickdb_v1_quickdb_proto_rawDesc = nil
+	file_proto_quickdb_v1_quickdb_proto_goTypes = nil
+	file_proto_quickdb_v1_quickdb_proto_depIdxs = nil
+}
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ context.Context
+var _ grpc.ClientConnInterface
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+const _ = grpc.SupportPackageIsVersion6
+
+// QuickClient is the client API for Quick service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
+type QuickClient interface {
+	CreateEphemeralDatabase(ctx context.Context, in *CreateEphemeralDatabaseRequest, opts ...grpc.CallOption) (*CreateEphemeralDatabaseResponse, error)
+	DropEphemeralDatabase(ctx context.Context, in *DropEphemeralDatabaseRequest, opts ...grpc.CallOption) (*DropEphemeralDatabaseResponse, error)
+}
+
+type quickClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewQuickClient(cc grpc.ClientConnInterface) QuickClient {
+	return &quickClient{cc}
+}
+
+func (c *quickClient) CreateEphemeralDatabase(ctx context.Context, in *CreateEphemeralDatabaseRequest, opts ...grpc.CallOption) (*CreateEphemeralDatabaseResponse, error) {
+	out := new(CreateEphemeralDatabaseResponse)
+	err := c.cc.Invoke(ctx, "/remote.sqlc.dev.quickdb.v1.Quick/CreateEphemeralDatabase", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *quickClient) DropEphemeralDatabase(ctx context.Context, in *DropEphemeralDatabaseRequest, opts ...grpc.CallOption) (*DropEphemeralDatabaseResponse, error) {
+	out := new(DropEphemeralDatabaseResponse)
+	err := c.cc.Invoke(ctx, "/remote.sqlc.dev.quickdb.v1.Quick/DropEphemeralDatabase", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// QuickServer is the server API for Quick service.
+type QuickServer interface {
+	CreateEphemeralDatabase(context.Context, *CreateEphemeralDatabaseRequest) (*CreateEphemeralDatabaseResponse, error)
+	DropEphemeralDatabase(context.Context, *DropEphemeralDatabaseRequest) (*DropEphemeralDatabaseResponse, error)
+}
+
+// UnimplementedQuickServer can be embedded to have forward compatible implementations.
+type UnimplementedQuickServer struct {
+}
+
+func (*UnimplementedQuickServer) CreateEphemeralDatabase(context.Context, *CreateEphemeralDatabaseRequest) (*CreateEphemeralDatabaseResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method CreateEphemeralDatabase not implemented")
+}
+func (*UnimplementedQuickServer) DropEphemeralDatabase(context.Context, *DropEphemeralDatabaseRequest) (*DropEphemeralDatabaseResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method DropEphemeralDatabase not implemented")
+}
+
+func RegisterQuickServer(s *grpc.Server, srv QuickServer) {
+	s.RegisterService(&_Quick_serviceDesc, srv)
+}
+
+func _Quick_CreateEphemeralDatabase_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(CreateEphemeralDatabaseRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(QuickServer).CreateEphemeralDatabase(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/remote.sqlc.dev.quickdb.v1.Quick/CreateEphemeralDatabase",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(QuickServer).CreateEphemeralDatabase(ctx, req.(*CreateEphemeralDatabaseRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Quick_DropEphemeralDatabase_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(DropEphemeralDatabaseRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(QuickServer).DropEphemeralDatabase(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/remote.sqlc.dev.quickdb.v1.Quick/DropEphemeralDatabase",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(QuickServer).DropEphemeralDatabase(ctx, req.(*DropEphemeralDatabaseRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+var _Quick_serviceDesc = grpc.ServiceDesc{
+	ServiceName: "remote.sqlc.dev.quickdb.v1.Quick",
+	HandlerType: (*QuickServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "CreateEphemeralDatabase",
+			Handler:    _Quick_CreateEphemeralDatabase_Handler,
+		},
+		{
+			MethodName: "DropEphemeralDatabase",
+			Handler:    _Quick_DropEphemeralDatabase_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "proto/quickdb/v1/quickdb.proto",
+}


Test patch:
diff --git a/internal/endtoend/ddl_test.go b/internal/endtoend/ddl_test.go
new file mode 100644
index 000000000..363ba8cbb
--- /dev/null
+++ b/internal/endtoend/ddl_test.go
@@ -0,0 +1,133 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/jackc/pgx/v5"
+
+	"github.com/sqlc-dev/sqlc/internal/config"
+	"github.com/sqlc-dev/sqlc/internal/migrations"
+	"github.com/sqlc-dev/sqlc/internal/quickdb"
+	pb "github.com/sqlc-dev/sqlc/internal/quickdb/v1"
+	"github.com/sqlc-dev/sqlc/internal/sql/sqlpath"
+)
+
+func TestValidSchema(t *testing.T) {
+	ctx := context.Background()
+
+	projectID := os.Getenv("DDL_SQLC_PROJECT_ID")
+	authToken := os.Getenv("DDL_SQLC_AUTH_TOKEN")
+
+	if projectID == "" || authToken == "" {
+		if os.Getenv("CI") == "" {
+			t.Skip("skiping ddl tests outside of CI")
+		} else {
+			t.Fatal("missing project id and auth token")
+		}
+	}
+
+	client, err := quickdb.NewClient(projectID, authToken)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	files := []string{}
+
+	// Find all tests that do not have a stderr.txt file
+	err = filepath.Walk("testdata", func(path string, info fs.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+		if filepath.Base(path) == "sqlc.json" || filepath.Base(path) == "sqlc.yaml" {
+			stderr := filepath.Join(filepath.Dir(path), "stderr.txt")
+			if _, err := os.Stat(stderr); !os.IsNotExist(err) {
+				return nil
+			}
+			files = append(files, path)
+		}
+		return nil
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for _, file := range files {
+		file := file // https://golang.org/doc/faq#closures_and_goroutines
+		rd, err := os.Open(file)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		conf, err := config.ParseConfig(rd)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		for j, pkg := range conf.SQL {
+			j, pkg := j, pkg
+			if pkg.Engine != config.EnginePostgreSQL {
+				continue
+			}
+			t.Run(fmt.Sprintf("endtoend-%s-%d", file, j), func(t *testing.T) {
+				t.Parallel()
+
+				var schema []string
+				for _, path := range pkg.Schema {
+					schema = append(schema, filepath.Join(filepath.Dir(file), path))
+				}
+
+				files, err := sqlpath.Glob(schema)
+				if err != nil {
+					t.Fatal(err)
+				}
+
+				var sqls []string
+				for _, f := range files {
+					contents, err := os.ReadFile(f)
+					if err != nil {
+						t.Fatalf("%s: %s", f, err)
+					}
+					// TODO: Split schema into separate files
+					before, _, _ := strings.Cut(string(contents), "-- name:")
+					before, _, _ = strings.Cut(before, "/* name:")
+					// Support loading pg_dump SQL files
+					before = strings.ReplaceAll(before, "CREATE SCHEMA public;", "CREATE SCHEMA IF NOT EXISTS public;")
+					sqls = append(sqls, migrations.RemoveRollbackStatements(before))
+				}
+
+				start := time.Now()
+				resp, err := client.CreateEphemeralDatabase(ctx, &pb.CreateEphemeralDatabaseRequest{
+					Engine:     "postgresql",
+					Region:     "iad",
+					Migrations: sqls,
+				})
+				t.Logf("%s", time.Since(start))
+				if err != nil {
+					t.Fatal(err)
+				}
+
+				t.Cleanup(func() {
+					_, err = client.DropEphemeralDatabase(ctx, &pb.DropEphemeralDatabaseRequest{
+						DatabaseId: resp.DatabaseId,
+					})
+					if err != nil {
+						t.Fatal(err)
+					}
+				})
+
+				conn, err := pgx.Connect(ctx, resp.Uri)
+				if err != nil {
+					t.Fatalf("connect %s: %s", resp.Uri, err)
+				}
+				defer conn.Close(ctx)
+			})
+		}
+	}
+}