github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/importccl/exportcsv_test.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Licensed as a CockroachDB Enterprise file under the Cockroach Community 4 // License (the "License"); you may not use this file except in compliance with 5 // the License. You may obtain a copy of the License at 6 // 7 // https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt 8 9 package importccl_test 10 11 import ( 12 "compress/gzip" 13 "context" 14 "fmt" 15 "io" 16 "io/ioutil" 17 "os" 18 "path/filepath" 19 "strings" 20 "testing" 21 22 "github.com/cockroachdb/cockroach/pkg/base" 23 "github.com/cockroachdb/cockroach/pkg/config" 24 "github.com/cockroachdb/cockroach/pkg/config/zonepb" 25 "github.com/cockroachdb/cockroach/pkg/keys" 26 "github.com/cockroachdb/cockroach/pkg/testutils" 27 "github.com/cockroachdb/cockroach/pkg/testutils/serverutils" 28 "github.com/cockroachdb/cockroach/pkg/testutils/sqlutils" 29 "github.com/cockroachdb/cockroach/pkg/testutils/testcluster" 30 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 31 "github.com/cockroachdb/cockroach/pkg/workload/bank" 32 "github.com/cockroachdb/cockroach/pkg/workload/workloadsql" 33 "github.com/gogo/protobuf/proto" 34 ) 35 36 func setupExportableBank(t *testing.T, nodes, rows int) (*sqlutils.SQLRunner, string, func()) { 37 ctx := context.Background() 38 dir, cleanupDir := testutils.TempDir(t) 39 40 tc := testcluster.StartTestCluster(t, nodes, 41 base.TestClusterArgs{ServerArgs: base.TestServerArgs{ExternalIODir: dir, UseDatabase: "test"}}, 42 ) 43 conn := tc.Conns[0] 44 db := sqlutils.MakeSQLRunner(conn) 45 db.Exec(t, "CREATE DATABASE test") 46 47 wk := bank.FromRows(rows) 48 l := workloadsql.InsertsDataLoader{BatchSize: 100, Concurrency: 3} 49 if _, err := workloadsql.Setup(ctx, conn, wk, l); err != nil { 50 t.Fatal(err) 51 } 52 53 config.TestingSetupZoneConfigHook(tc.Stopper()) 54 v, err := tc.Servers[0].DB().Get(context.Background(), keys.SystemSQLCodec.DescIDSequenceKey()) 55 if err != nil { 56 t.Fatal(err) 57 } 58 last := uint32(v.ValueInt()) 59 zoneConfig := zonepb.DefaultZoneConfig() 60 zoneConfig.RangeMaxBytes = proto.Int64(5000) 61 config.TestingSetZoneConfig(last+1, zoneConfig) 62 db.Exec(t, "ALTER TABLE bank SCATTER") 63 db.Exec(t, "SELECT 'force a scan to repopulate range cache' FROM [SELECT count(*) FROM bank]") 64 65 return db, dir, func() { 66 tc.Stopper().Stop(ctx) 67 cleanupDir() 68 } 69 } 70 71 func TestExportImportBank(t *testing.T) { 72 defer leaktest.AfterTest(t)() 73 74 db, dir, cleanup := setupExportableBank(t, 3, 100) 75 defer cleanup() 76 77 // Add some unicode to prove FmtExport works as advertised. 78 db.Exec(t, "UPDATE bank SET payload = payload || '✅' WHERE id = 5") 79 db.Exec(t, "UPDATE bank SET payload = NULL WHERE id % 2 = 0") 80 81 chunkSize := 13 82 for _, null := range []string{"", "NULL"} { 83 nullAs, nullIf := "", ", nullif = ''" 84 if null != "" { 85 nullAs = fmt.Sprintf(", nullas = '%s'", null) 86 nullIf = fmt.Sprintf(", nullif = '%s'", null) 87 } 88 t.Run("null="+null, func(t *testing.T) { 89 var files []string 90 91 var asOf string 92 db.QueryRow(t, "SELECT cluster_logical_timestamp()").Scan(&asOf) 93 94 for _, row := range db.QueryStr(t, 95 fmt.Sprintf(`EXPORT INTO CSV 'nodelocal://0/t' 96 WITH chunk_rows = $1, delimiter = '|' %s 97 FROM SELECT * FROM bank AS OF SYSTEM TIME %s`, nullAs, asOf), chunkSize, 98 ) { 99 files = append(files, row[0]) 100 f, err := ioutil.ReadFile(filepath.Join(dir, "t", row[0])) 101 if err != nil { 102 t.Fatal(err) 103 } 104 t.Log(string(f)) 105 } 106 107 schema := bank.FromRows(1).Tables()[0].Schema 108 fileList := "'nodelocal://0/t/" + strings.Join(files, "', 'nodelocal://0/t/") + "'" 109 db.Exec(t, fmt.Sprintf(`IMPORT TABLE bank2 %s CSV DATA (%s) WITH delimiter = '|'%s`, schema, fileList, nullIf)) 110 111 db.CheckQueryResults(t, 112 fmt.Sprintf(`SELECT * FROM bank AS OF SYSTEM TIME %s ORDER BY id`, asOf), db.QueryStr(t, `SELECT * FROM bank2 ORDER BY id`), 113 ) 114 db.CheckQueryResults(t, 115 `SHOW EXPERIMENTAL_FINGERPRINTS FROM TABLE bank2`, db.QueryStr(t, `SHOW EXPERIMENTAL_FINGERPRINTS FROM TABLE bank`), 116 ) 117 db.Exec(t, "DROP TABLE bank2") 118 }) 119 } 120 } 121 122 func TestMultiNodeExportStmt(t *testing.T) { 123 defer leaktest.AfterTest(t)() 124 125 nodes := 5 126 exportRows := 100 127 db, _, cleanup := setupExportableBank(t, nodes, exportRows*2) 128 defer cleanup() 129 130 maxTries := 10 131 // we might need to retry if our table didn't actually scatter enough. 132 for tries := 0; tries < maxTries; tries++ { 133 chunkSize := 13 134 rows := db.Query(t, 135 `EXPORT INTO CSV 'nodelocal://0/t' WITH chunk_rows = $3 FROM SELECT * FROM bank WHERE id >= $1 and id < $2`, 136 10, 10+exportRows, chunkSize, 137 ) 138 139 files, totalRows, totalBytes := 0, 0, 0 140 nodesSeen := make(map[string]bool) 141 for rows.Next() { 142 filename, count, bytes := "", 0, 0 143 if err := rows.Scan(&filename, &count, &bytes); err != nil { 144 t.Fatal(err) 145 } 146 files++ 147 if count > chunkSize { 148 t.Fatalf("expected no chunk larger than %d, got %d", chunkSize, count) 149 } 150 totalRows += count 151 totalBytes += bytes 152 nodesSeen[strings.SplitN(filename, ".", 2)[0]] = true 153 } 154 if totalRows != exportRows { 155 t.Fatalf("Expected %d rows, got %d", exportRows, totalRows) 156 } 157 if expected := exportRows / chunkSize; files < expected { 158 t.Fatalf("expected at least %d files, got %d", expected, files) 159 } 160 if len(nodesSeen) < 2 { 161 // table isn't as scattered as we expected, but we can try again. 162 if tries < maxTries { 163 continue 164 } 165 t.Fatalf("expected files from %d nodes, got %d: %v", 2, len(nodesSeen), nodesSeen) 166 } 167 break 168 } 169 } 170 171 func TestExportJoin(t *testing.T) { 172 defer leaktest.AfterTest(t)() 173 dir, cleanupDir := testutils.TempDir(t) 174 defer cleanupDir() 175 176 srv, db, _ := serverutils.StartServer(t, base.TestServerArgs{ExternalIODir: dir}) 177 defer srv.Stopper().Stop(context.Background()) 178 sqlDB := sqlutils.MakeSQLRunner(db) 179 180 sqlDB.Exec(t, `CREATE TABLE t AS VALUES (1, 2)`) 181 sqlDB.Exec(t, `EXPORT INTO CSV 'nodelocal://0/join' FROM SELECT * FROM t, t as u`) 182 } 183 184 func TestExportOrder(t *testing.T) { 185 defer leaktest.AfterTest(t)() 186 dir, cleanupDir := testutils.TempDir(t) 187 defer cleanupDir() 188 189 srv, db, _ := serverutils.StartServer(t, base.TestServerArgs{ExternalIODir: dir}) 190 defer srv.Stopper().Stop(context.Background()) 191 sqlDB := sqlutils.MakeSQLRunner(db) 192 193 sqlDB.Exec(t, `create table foo (i int primary key, x int, y int, z int, index (y))`) 194 sqlDB.Exec(t, `insert into foo values (1, 12, 3, 14), (2, 22, 2, 24), (3, 32, 1, 34)`) 195 196 sqlDB.Exec(t, `EXPORT INTO CSV 'nodelocal://0/order' from select * from foo order by y asc limit 2`) 197 content, err := ioutil.ReadFile(filepath.Join(dir, "order", "n1.0.csv")) 198 if err != nil { 199 t.Fatal(err) 200 } 201 if expected, got := "3,32,1,34\n2,22,2,24\n", string(content); expected != got { 202 t.Fatalf("expected %q, got %q", expected, got) 203 } 204 } 205 206 func TestExportOrderCompressed(t *testing.T) { 207 defer leaktest.AfterTest(t)() 208 dir, cleanupDir := testutils.TempDir(t) 209 defer cleanupDir() 210 211 var close = func(c io.Closer) { 212 if err := c.Close(); err != nil { 213 t.Fatalf("failed to close stream, got error %s", err) 214 } 215 } 216 217 srv, db, _ := serverutils.StartServer(t, base.TestServerArgs{ExternalIODir: dir}) 218 defer srv.Stopper().Stop(context.Background()) 219 sqlDB := sqlutils.MakeSQLRunner(db) 220 221 sqlDB.Exec(t, `create table foo (i int primary key, x int, y int, z int, index (y))`) 222 sqlDB.Exec(t, `insert into foo values (1, 12, 3, 14), (2, 22, 2, 24), (3, 32, 1, 34)`) 223 224 sqlDB.Exec(t, `EXPORT INTO CSV 'nodelocal://0/order' with compression = gzip from select * from foo order by y asc limit 2`) 225 fi, err := os.Open(filepath.Join(dir, "order", "n1.0.csv.gz")) 226 defer close(fi) 227 228 if err != nil { 229 t.Fatal(err) 230 } 231 232 gzipReader, err := gzip.NewReader(fi) 233 defer close(gzipReader) 234 235 if err != nil { 236 t.Fatal(err) 237 } 238 239 content, err := ioutil.ReadAll(gzipReader) 240 if err != nil { 241 t.Fatal(err) 242 } 243 244 if expected, got := "3,32,1,34\n2,22,2,24\n", string(content); expected != got { 245 t.Fatalf("expected %q, got %q", expected, got) 246 } 247 } 248 249 func TestExportShow(t *testing.T) { 250 defer leaktest.AfterTest(t)() 251 dir, cleanupDir := testutils.TempDir(t) 252 defer cleanupDir() 253 254 srv, db, _ := serverutils.StartServer(t, base.TestServerArgs{ExternalIODir: dir}) 255 defer srv.Stopper().Stop(context.Background()) 256 sqlDB := sqlutils.MakeSQLRunner(db) 257 258 sqlDB.Exec(t, `EXPORT INTO CSV 'nodelocal://0/show' FROM SELECT * FROM [SHOW DATABASES] ORDER BY database_name`) 259 content, err := ioutil.ReadFile(filepath.Join(dir, "show", "n1.0.csv")) 260 if err != nil { 261 t.Fatal(err) 262 } 263 if expected, got := "defaultdb\npostgres\nsystem\n", string(content); expected != got { 264 t.Fatalf("expected %q, got %q", expected, got) 265 } 266 } 267 268 // TestExportVectorized makes sure that SupportsVectorized check doesn't panic 269 // on CSVWriter processor. 270 func TestExportVectorized(t *testing.T) { 271 defer leaktest.AfterTest(t)() 272 dir, cleanupDir := testutils.TempDir(t) 273 defer cleanupDir() 274 275 srv, db, _ := serverutils.StartServer(t, base.TestServerArgs{ExternalIODir: dir}) 276 defer srv.Stopper().Stop(context.Background()) 277 sqlDB := sqlutils.MakeSQLRunner(db) 278 279 sqlDB.Exec(t, `CREATE TABLE t(a INT PRIMARY KEY)`) 280 sqlDB.Exec(t, `SET vectorize_row_count_threshold=0`) 281 sqlDB.Exec(t, `EXPORT INTO CSV 'http://0.1:37957/exp_1' FROM TABLE t`) 282 }