This is an automated email from the ASF dual-hosted git repository.
lidongdai pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git
The following commit(s) were added to refs/heads/dev by this push:
new 28b65d3fef [Feature][Connector-v2]Resolve null first column in CSV
Reader (#10383)
28b65d3fef is described below
commit 28b65d3fefa882cfe7361f7ae6941b6d701527f4
Author: zhangdonghao <[email protected]>
AuthorDate: Tue Feb 3 19:02:35 2026 +0800
[Feature][Connector-v2]Resolve null first column in CSV Reader (#10383)
---
.../file/source/reader/CsvReadStrategy.java | 114 ++++++++++-------
.../file/source/reader/CsvReadStrategyTest.java | 55 ++++++++
.../test/resources/csv/utf8_bom_with_header.csv | 3 +
.../test/resources/csv/utf8_bom_without_header.csv | 2 +
.../file/local/SplitFileStrategyTest.java | 141 +++++++++++++++++++++
.../src/test/resources/utf8_bom_split.csv | 101 +++++++++++++++
6 files changed, 370 insertions(+), 46 deletions(-)
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/CsvReadStrategy.java
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/CsvReadStrategy.java
index e9b027c96b..665c2670ed 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/CsvReadStrategy.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/CsvReadStrategy.java
@@ -24,6 +24,7 @@ import org.apache.seatunnel.api.configuration.ReadonlyConfig;
import org.apache.seatunnel.api.source.Collector;
import org.apache.seatunnel.api.table.catalog.CatalogTable;
import org.apache.seatunnel.api.table.catalog.CatalogTableUtil;
+import org.apache.seatunnel.api.table.catalog.Column;
import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
import org.apache.seatunnel.api.table.type.SeaTunnelRow;
import org.apache.seatunnel.api.table.type.SeaTunnelRowType;
@@ -44,6 +45,7 @@ import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVFormat.Builder;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
+import org.apache.commons.io.input.BOMInputStream;
import io.airlift.compress.lzo.LzopCodec;
import lombok.extern.slf4j.Slf4j;
@@ -52,7 +54,9 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -97,42 +101,15 @@ public class CsvReadStrategy extends AbstractReadStrategy {
Map<String, String> partitionsMap,
String currentFileName)
throws IOException {
- InputStream actualInputStream;
- switch (compressFormat) {
- case LZO:
- LzopCodec lzo = new LzopCodec();
- actualInputStream = lzo.createInputStream(inputStream);
- break;
- case NONE:
- actualInputStream = inputStream;
- break;
- default:
- log.warn(
- "Csv file does not support this compress type: {}",
- compressFormat.getCompressCodec());
- actualInputStream = inputStream;
- break;
- }
- // rebuild inputStream
- if (enableSplitFile && split.getLength() > -1) {
- actualInputStream = safeSlice(inputStream, split.getStart(),
split.getLength());
- }
- CSVFormat csvFormat = getCSVFormat();
- // if enableSplitFile is true,no need to skip
- if (!enableSplitFile) {
- if (firstLineAsHeader) {
- csvFormat = csvFormat.withFirstRecordAsHeader();
- }
- }
- try (BufferedReader reader =
- new BufferedReader(new
InputStreamReader(actualInputStream, encoding));
- CSVParser csvParser = new CSVParser(reader, csvFormat); ) {
- // test and skip `\uFEFF` BOM
- reader.mark(1);
- int firstChar = reader.read();
- if (firstChar != 0xFEFF) {
- reader.reset();
- }
+ log.info(
+ "Start reading CSV file: {}, split start: {}, split length:
{}",
+ currentFileName,
+ split.getStart(),
+ split.getLength());
+ try (BOMInputStream bomIn = new
BOMInputStream(wrapInputStream(inputStream, split));
+ BufferedReader reader =
+ new BufferedReader(new InputStreamReader(bomIn,
getCharset(bomIn)));
+ CSVParser csvParser = new CSVParser(reader,
getCSVFormat(split))) {
// skip lines
// if enableSplitFile is true,no need to skip
if (!enableSplitFile) {
@@ -145,15 +122,23 @@ public class CsvReadStrategy extends AbstractReadStrategy
{
}
}
}
- // read lines
- List<String> headers = getHeaders(csvParser);
+ // read header lines
+ List<String> headers = getHeaders(csvParser, split);
+ // Clean up BOM characters (\uFEFF) in the header to solve
occasional BOM residue
+ // issues
+ List<String> cleanedHeaders =
+ headers.stream()
+ .map(header -> header.replace("\uFEFF", ""))
+ .collect(Collectors.toList());
for (CSVRecord csvRecord : csvParser) {
HashMap<Integer, String> fieldIdValueMap = new HashMap<>();
- for (int i = 0; i < headers.size(); i++) {
+ for (int i = 0; i < cleanedHeaders.size(); i++) {
// the user input schema may not contain all the columns
in the csv header
// and may contain columns in a different order with the
csv header
int index =
-
inputCatalogTable.getSeaTunnelRowType().indexOf(headers.get(i), false);
+ inputCatalogTable
+ .getSeaTunnelRowType()
+ .indexOf(cleanedHeaders.get(i), false);
if (index == -1) {
continue;
}
@@ -192,7 +177,39 @@ public class CsvReadStrategy extends AbstractReadStrategy {
}
}
- private CSVFormat getCSVFormat() {
+ private InputStream wrapInputStream(InputStream inputStream,
FileSourceSplit split)
+ throws IOException {
+ InputStream resultStream;
+ // process compression isnputStream
+ switch (compressFormat) {
+ case LZO:
+ LzopCodec lzo = new LzopCodec();
+ resultStream = lzo.createInputStream(inputStream);
+ break;
+ case NONE:
+ resultStream = inputStream;
+ break;
+ default:
+ log.warn(
+ "Csv file does not support this compress type: {}",
+ compressFormat.getCompressCodec());
+ resultStream = inputStream;
+ break;
+ }
+ // rebuild inputStream
+ if (enableSplitFile && split.getLength() > -1) {
+ resultStream = safeSlice(resultStream, split.getStart(),
split.getLength());
+ }
+ return resultStream;
+ }
+
+ private Charset getCharset(BOMInputStream bomIn) throws IOException {
+ return bomIn.getBOM() == null
+ ? Charset.forName(encoding)
+ : Charset.forName(bomIn.getBOM().getCharsetName());
+ }
+
+ private CSVFormat getCSVFormat(FileSourceSplit split) {
String quoteChar =
readonlyConfig.get(FileBaseSourceOptions.QUOTE_CHAR);
String escapeChar =
readonlyConfig.get(FileBaseSourceOptions.ESCAPE_CHAR);
Builder builder =
@@ -203,17 +220,22 @@ public class CsvReadStrategy extends AbstractReadStrategy
{
if (StringUtils.isNotEmpty(escapeChar)) {
builder.setEscape(escapeChar.charAt(0));
}
- return builder.build();
+ CSVFormat csvFormat = builder.build();
+ // if enableSplitFile is true,no need to skip
+ if (firstLineAsHeader && (!enableSplitFile || split.getStart() == 0)) {
+ csvFormat = csvFormat.withFirstRecordAsHeader();
+ }
+ return csvFormat;
}
- private List<String> getHeaders(CSVParser csvParser) {
+ private List<String> getHeaders(CSVParser csvParser, FileSourceSplit
split) {
List<String> headers;
- if (firstLineAsHeader) {
- headers =
csvParser.getHeaderNames().stream().collect(Collectors.toList());
+ if (firstLineAsHeader && (!enableSplitFile || split.getStart() == 0)) {
+ headers = new ArrayList<>(csvParser.getHeaderNames());
} else {
headers =
inputCatalogTable.getTableSchema().getColumns().stream()
- .map(column -> column.getName())
+ .map(Column::getName)
.collect(Collectors.toList());
}
return headers;
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/CsvReadStrategyTest.java
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/CsvReadStrategyTest.java
index 78574fc14a..681963468f 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/CsvReadStrategyTest.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/CsvReadStrategyTest.java
@@ -20,6 +20,7 @@ package
org.apache.seatunnel.connectors.seatunnel.file.source.reader;
import org.apache.seatunnel.shade.com.typesafe.config.ConfigFactory;
import org.apache.seatunnel.api.source.Collector;
+import org.apache.seatunnel.api.table.catalog.CatalogTable;
import org.apache.seatunnel.api.table.catalog.CatalogTableUtil;
import org.apache.seatunnel.api.table.type.BasicType;
import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
@@ -141,6 +142,54 @@ public class CsvReadStrategyTest {
}
}
+ @Test
+ public void testUtf8BomCsvRead() throws Exception {
+ CatalogTable catalogTable =
+ CatalogTableUtil.getCatalogTable(
+ "test",
+ new SeaTunnelRowType(
+ new String[] {"id", "name", "age", "gender"},
+ new SeaTunnelDataType[] {
+ BasicType.INT_TYPE,
+ BasicType.STRING_TYPE,
+ BasicType.INT_TYPE,
+ BasicType.STRING_TYPE
+ }));
+ URL resource =
CsvReadStrategyTest.class.getResource("/csv/utf8_bom_with_header.csv");
+ Map<String, Object> csvBomOptions = getCsvBomOptions(true);
+ checkCsvBomRead(resource, csvBomOptions, catalogTable);
+
+ URL resource1 =
CsvReadStrategyTest.class.getResource("/csv/utf8_bom_without_header.csv");
+ Map<String, Object> csvBomOptions1 = getCsvBomOptions(false);
+ checkCsvBomRead(resource1, csvBomOptions1, catalogTable);
+ }
+
+ private void checkCsvBomRead(
+ URL resource, Map<String, Object> csvBomOptions, CatalogTable
catalogTable)
+ throws Exception {
+ String path = Paths.get(resource.toURI()).toString();
+ TestCollector testCollector;
+ try (CsvReadStrategy csvReadStrategy = new CsvReadStrategy()) {
+ LocalConf localConf = new LocalConf(FS_DEFAULT_NAME_DEFAULT);
+ csvReadStrategy.init(localConf);
+ csvReadStrategy.getFileNamesByPath(path);
+
csvReadStrategy.setPluginConfig(ConfigFactory.parseMap(csvBomOptions));
+ csvReadStrategy.setCatalogTable(catalogTable);
+ testCollector = new TestCollector();
+ csvReadStrategy.read(path, "", testCollector);
+ }
+ final List<SeaTunnelRow> rows = testCollector.getRows();
+ Assertions.assertEquals(2, rows.size());
+ Assertions.assertEquals(9821, rows.get(0).getField(0));
+ Assertions.assertEquals("hawk", rows.get(0).getField(1));
+ Assertions.assertEquals(37, rows.get(0).getField(2));
+ Assertions.assertEquals("M", rows.get(0).getField(3));
+ Assertions.assertEquals(9822, rows.get(1).getField(0));
+ Assertions.assertEquals("jack", rows.get(1).getField(1));
+ Assertions.assertEquals(18, rows.get(1).getField(2));
+ Assertions.assertEquals("M", rows.get(1).getField(3));
+ }
+
private boolean isWindows() {
return System.getProperty("os.name").toLowerCase().contains("win");
}
@@ -152,6 +201,12 @@ public class CsvReadStrategyTest {
return map;
}
+ private Map<String, Object> getCsvBomOptions(boolean withHeader) {
+ Map<String, Object> map = new HashMap<>();
+ map.put(FileBaseSourceOptions.CSV_USE_HEADER_LINE.key(), withHeader);
+ return map;
+ }
+
public static class TestCollector implements Collector<SeaTunnelRow> {
private final List<SeaTunnelRow> rows = new ArrayList<>();
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/resources/csv/utf8_bom_with_header.csv
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/resources/csv/utf8_bom_with_header.csv
new file mode 100644
index 0000000000..802ed3914d
--- /dev/null
+++
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/resources/csv/utf8_bom_with_header.csv
@@ -0,0 +1,3 @@
+id,name,age,gender
+9821,hawk,37,M
+9822,jack,18,M
\ No newline at end of file
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/resources/csv/utf8_bom_without_header.csv
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/resources/csv/utf8_bom_without_header.csv
new file mode 100644
index 0000000000..d14fcfe49f
--- /dev/null
+++
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/resources/csv/utf8_bom_without_header.csv
@@ -0,0 +1,2 @@
+9821,hawk,37,M
+9822,jack,18,M
\ No newline at end of file
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/local/SplitFileStrategyTest.java
b/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/local/SplitFileStrategyTest.java
index e2c1f1a09c..2c05c66725 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/local/SplitFileStrategyTest.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/local/SplitFileStrategyTest.java
@@ -16,7 +16,21 @@
*/
package org.apache.seatunnel.connectors.seatunnel.file.local;
+import org.apache.seatunnel.shade.com.typesafe.config.ConfigFactory;
+
+import org.apache.seatunnel.api.source.Collector;
+import org.apache.seatunnel.api.table.catalog.CatalogTable;
+import org.apache.seatunnel.api.table.catalog.CatalogTableUtil;
+import org.apache.seatunnel.api.table.type.BasicType;
+import org.apache.seatunnel.api.table.type.LocalTimeType;
+import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
+import org.apache.seatunnel.api.table.type.SeaTunnelRow;
+import org.apache.seatunnel.api.table.type.SeaTunnelRowType;
+import
org.apache.seatunnel.connectors.seatunnel.file.config.FileBaseSourceOptions;
+import org.apache.seatunnel.connectors.seatunnel.file.config.HadoopConf;
import
org.apache.seatunnel.connectors.seatunnel.file.local.config.LocalFileHadoopConf;
+import
org.apache.seatunnel.connectors.seatunnel.file.local.source.split.LocalFileAccordingToSplitSizeSplitStrategy;
+import
org.apache.seatunnel.connectors.seatunnel.file.source.reader.CsvReadStrategy;
import
org.apache.seatunnel.connectors.seatunnel.file.source.split.AccordingToSplitSizeSplitStrategy;
import
org.apache.seatunnel.connectors.seatunnel.file.source.split.FileSourceSplit;
@@ -25,11 +39,17 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
+import lombok.Getter;
import lombok.SneakyThrows;
import java.net.URL;
import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+
+import static
org.apache.hadoop.fs.CommonConfigurationKeysPublic.FS_DEFAULT_NAME_DEFAULT;
public class SplitFileStrategyTest {
@@ -171,4 +191,125 @@ public class SplitFileStrategyTest {
Assertions.assertEquals(0, splits.size());
}
}
+
+ @Test
+ public void testUtf8BomCsvSplitRead() throws Exception {
+ String realPath;
+ final List<FileSourceSplit> splits;
+ try (LocalFileAccordingToSplitSizeSplitStrategy localFileSplitStrategy
=
+ new LocalFileAccordingToSplitSizeSplitStrategy("\n", 0L,
"utf-8", 1024 * 5L)) {
+ URL url =
getClass().getClassLoader().getResource("utf8_bom_split.csv");
+ realPath = Paths.get(url.toURI()).toString();
+ splits = localFileSplitStrategy.split("test.table", realPath);
+ }
+ Assertions.assertEquals(3, splits.size());
+
+ CatalogTable catalogTable =
+ CatalogTableUtil.getCatalogTable(
+ "test",
+ new SeaTunnelRowType(
+ new String[] {
+ "id",
+ "username",
+ "email",
+ "phone",
+ "address",
+ "city",
+ "province",
+ "country",
+ "zip_code",
+ "register_date",
+ "login_time",
+ "total_score",
+ "avg_score",
+ "is_active"
+ },
+ new SeaTunnelDataType[] {
+ BasicType.INT_TYPE,
+ BasicType.STRING_TYPE,
+ BasicType.STRING_TYPE,
+ BasicType.STRING_TYPE,
+ BasicType.STRING_TYPE,
+ BasicType.STRING_TYPE,
+ BasicType.STRING_TYPE,
+ BasicType.STRING_TYPE,
+ BasicType.STRING_TYPE,
+ BasicType.STRING_TYPE,
+ LocalTimeType.LOCAL_DATE_TIME_TYPE,
+ BasicType.INT_TYPE,
+ BasicType.INT_TYPE,
+ BasicType.BOOLEAN_TYPE
+ }));
+
+ TestCollector testCollector;
+ try (CsvReadStrategy csvReadStrategy = new CsvReadStrategy()) {
+ LocalConf localConf = new LocalConf(FS_DEFAULT_NAME_DEFAULT);
+ csvReadStrategy.init(localConf);
+ csvReadStrategy.getFileNamesByPath(realPath);
+
csvReadStrategy.setPluginConfig(ConfigFactory.parseMap(getCsvBomOptions()));
+ csvReadStrategy.setCatalogTable(catalogTable);
+ testCollector = new TestCollector();
+ for (FileSourceSplit split : splits) {
+ csvReadStrategy.read(split, testCollector);
+ }
+ }
+ List<SeaTunnelRow> rows = testCollector.getRows();
+ Assertions.assertEquals(100, rows.size());
+
+ for (int rowIdx = 0; rowIdx < rows.size(); rowIdx++) {
+ SeaTunnelRow currentRow = rows.get(rowIdx);
+ int columnCount = currentRow.getFields().length;
+ for (int colIdx = 0; colIdx < columnCount; colIdx++) {
+ Object fieldValue = currentRow.getField(colIdx);
+ Assertions.assertNotNull(
+ fieldValue,
+ String.format(
+ "Field value at row %d, column %d is null",
+ rowIdx + 1, colIdx + 1));
+ }
+ }
+ }
+
+ private Map<String, Object> getCsvBomOptions() {
+ Map<String, Object> map = new HashMap<>();
+ map.put(FileBaseSourceOptions.CSV_USE_HEADER_LINE.key(), true);
+ map.put(FileBaseSourceOptions.ENABLE_FILE_SPLIT.key(), true);
+ map.put(FileBaseSourceOptions.FILE_SPLIT_SIZE.key(), 1024 * 5L);
+ return map;
+ }
+
+ @Getter
+ public static class TestCollector implements Collector<SeaTunnelRow> {
+
+ private final List<SeaTunnelRow> rows = new ArrayList<>();
+
+ @Override
+ public void collect(SeaTunnelRow record) {
+ rows.add(record);
+ }
+
+ @Override
+ public Object getCheckpointLock() {
+ return null;
+ }
+ }
+
+ public static class LocalConf extends HadoopConf {
+ private static final String HDFS_IMPL =
"org.apache.hadoop.fs.LocalFileSystem";
+ private static final String SCHEMA = "file";
+
+ public LocalConf(String hdfsNameKey) {
+ super(hdfsNameKey);
+ }
+
+ @Override
+ public String getFsHdfsImpl() {
+ return HDFS_IMPL;
+ }
+
+ @Override
+ public String getSchema() {
+ return SCHEMA;
+ }
+ }
}
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/resources/utf8_bom_split.csv
b/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/resources/utf8_bom_split.csv
new file mode 100644
index 0000000000..0b711e546f
--- /dev/null
+++
b/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/resources/utf8_bom_split.csv
@@ -0,0 +1,101 @@
+id,username,email,phone,address,city,province,country,zip_code,register_date,login_time,total_score,avg_score,is_active
+1,user_000001,[email protected],13531429286,Street 416, Block
D,City_11,Province_5,Country_CN,405230,2022-09-11 00:00:00,170,86,true
+2,user_000002,[email protected],13077789440,Street 214, Block
B,City_43,Province_5,Country_EU,844320,2022-01-23 00:00:00,482,69,false
+3,user_000003,[email protected],13434394477,Street 144, Block
C,City_29,Province_10,Country_JP,917959,2023-03-30 00:00:00,132,77,false
+4,user_000004,[email protected],13476751307,Street 269, Block
B,City_18,Province_3,Country_JP,386183,2023-10-16 00:00:00,402,99,true
+5,user_000005,[email protected],13053975977,Street 918, Block
D,City_40,Province_7,Country_CN,347027,2024-05-12 00:00:00,787,100,false
+6,user_000006,[email protected],13398216646,Street 578, Block
D,City_46,Province_18,Country_CN,578404,2020-10-26 00:00:00,111,65,false
+7,user_000007,[email protected],13645767519,Street 311, Block
A,City_44,Province_12,Country_CN,415307,2025-09-13 00:00:00,265,68,true
+8,user_000008,[email protected],13481617532,Street 301, Block
B,City_8,Province_17,Country_US,368159,2023-09-19 00:00:00,799,73,false
+9,user_000009,[email protected],13987203505,Street 805, Block
D,City_49,Province_20,Country_JP,300620,2020-07-05 00:00:00,971,92,false
+10,user_000010,[email protected],13029035162,Street 940, Block
B,City_45,Province_11,Country_US,792141,2024-10-27 00:00:00,155,66,false
+11,user_000011,[email protected],13435770755,Street 556, Block
B,City_20,Province_20,Country_JP,955360,2025-03-16 00:00:00,540,84,false
+12,user_000012,[email protected],13705902059,Street 243, Block
C,City_5,Province_14,Country_US,166810,2024-10-13 00:00:00,622,79,true
+13,user_000013,[email protected],13745419599,Street 216, Block
D,City_1,Province_4,Country_JP,307931,2023-04-11 00:00:00,162,64,true
+14,user_000014,[email protected],13911669494,Street 217, Block
C,City_49,Province_11,Country_US,458551,2021-07-10 00:00:00,593,90,false
+15,user_000015,[email protected],13595203673,Street 934, Block
D,City_41,Province_1,Country_EU,394676,2023-11-20 00:00:00,956,63,false
+16,user_000016,[email protected],13706000738,Street 18, Block
D,City_28,Province_18,Country_EU,348489,2020-08-07 00:00:00,34,72,false
+17,user_000017,[email protected],13783478473,Street 206, Block
C,City_19,Province_8,Country_EU,675214,2020-12-06 00:00:00,744,80,false
+18,user_000018,[email protected],13201211464,Street 98, Block
B,City_35,Province_10,Country_CN,917037,2023-09-08 00:00:00,767,89,false
+19,user_000019,[email protected],13023268406,Street 736, Block
D,City_4,Province_11,Country_US,926937,2020-01-11 00:00:00,790,98,false
+20,user_000020,[email protected],13187997559,Street 433, Block
D,City_44,Province_5,Country_CN,652908,2024-07-14 00:00:00,197,62,true
+21,user_000021,[email protected],13032345985,Street 106, Block
B,City_4,Province_6,Country_CN,107259,2023-03-22 00:00:00,574,76,true
+22,user_000022,[email protected],13008879737,Street 36, Block
B,City_1,Province_14,Country_JP,750327,2022-11-13 00:00:00,186,91,true
+23,user_000023,[email protected],13047201017,Street 57, Block
A,City_23,Province_19,Country_EU,495841,2020-06-25 00:00:00,701,65,false
+24,user_000024,[email protected],13807214468,Street 927, Block
D,City_44,Province_14,Country_CN,126031,2021-04-06 00:00:00,413,85,false
+25,user_000025,[email protected],13676770046,Street 669, Block
B,City_17,Province_6,Country_US,695559,2022-10-03 00:00:00,495,62,false
+26,user_000026,[email protected],13842462958,Street 610, Block
B,City_32,Province_14,Country_JP,558218,2020-08-11 00:00:00,281,66,false
+27,user_000027,[email protected],13376122955,Street 186, Block
D,City_2,Province_2,Country_US,514989,2020-06-04 00:00:00,262,66,false
+28,user_000028,[email protected],13089578049,Street 538, Block
D,City_43,Province_17,Country_EU,200415,2025-10-02 00:00:00,810,64,true
+29,user_000029,[email protected],13129476791,Street 984, Block
D,City_11,Province_6,Country_JP,439850,2022-12-08 00:00:00,905,80,false
+30,user_000030,[email protected],13925022099,Street 779, Block
C,City_21,Province_4,Country_JP,554146,2024-07-21 00:00:00,782,96,true
+31,user_000031,[email protected],13643367043,Street 14, Block
D,City_11,Province_18,Country_US,218096,2020-02-26 00:00:00,180,68,true
+32,user_000032,[email protected],13448692621,Street 167, Block
A,City_38,Province_11,Country_US,151668,2020-09-18 00:00:00,574,72,false
+33,user_000033,[email protected],13823923251,Street 686, Block
D,City_48,Province_5,Country_US,627363,2020-08-19 00:00:00,742,97,false
+34,user_000034,[email protected],13938869386,Street 272, Block
A,City_34,Province_4,Country_CN,504055,2020-09-13 00:00:00,506,85,true
+35,user_000035,[email protected],13356713245,Street 504, Block
C,City_46,Province_11,Country_JP,998239,2021-07-22 00:00:00,418,94,false
+36,user_000036,[email protected],13720537060,Street 786, Block
B,City_47,Province_12,Country_JP,780092,2023-01-18 00:00:00,829,64,false
+37,user_000037,[email protected],13491321527,Street 807, Block
B,City_2,Province_19,Country_EU,370815,2023-12-07 00:00:00,108,64,true
+38,user_000038,[email protected],13481242435,Street 338, Block
D,City_15,Province_17,Country_EU,747969,2025-05-03 00:00:00,831,81,true
+39,user_000039,[email protected],13213809370,Street 141, Block
B,City_10,Province_1,Country_CN,476357,2023-12-11 00:00:00,611,86,true
+40,user_000040,[email protected],13232553021,Street 389, Block
D,City_18,Province_2,Country_EU,301090,2023-05-03 00:00:00,19,83,false
+41,user_000041,[email protected],13232089055,Street 313, Block
A,City_2,Province_13,Country_JP,633570,2020-03-10 00:00:00,602,66,false
+42,user_000042,[email protected],13938610515,Street 856, Block
D,City_49,Province_11,Country_CN,786763,2021-05-07 00:00:00,630,71,false
+43,user_000043,[email protected],13112544447,Street 988, Block
A,City_22,Province_15,Country_CN,684355,2020-04-09 00:00:00,56,60,true
+44,user_000044,[email protected],13181878864,Street 892, Block
B,City_34,Province_9,Country_US,504636,2022-02-23 00:00:00,403,92,false
+45,user_000045,[email protected],13726641337,Street 804, Block
C,City_2,Province_12,Country_US,810378,2021-03-31 00:00:00,124,99,false
+46,user_000046,[email protected],13433048342,Street 370, Block
D,City_44,Province_20,Country_CN,667267,2021-04-14 00:00:00,492,74,false
+47,user_000047,[email protected],13341003050,Street 341, Block
A,City_7,Province_14,Country_US,661043,2024-10-15 00:00:00,153,94,false
+48,user_000048,[email protected],13449060455,Street 988, Block
D,City_4,Province_3,Country_EU,954213,2025-06-05 00:00:00,863,73,false
+49,user_000049,[email protected],13824103340,Street 671, Block
D,City_21,Province_5,Country_EU,847809,2022-04-04 00:00:00,929,78,true
+50,user_000050,[email protected],13448361238,Street 382, Block
D,City_37,Province_4,Country_EU,474068,2024-11-11 00:00:00,557,61,true
+51,user_000051,[email protected],13254298839,Street 326, Block
C,City_45,Province_11,Country_EU,182126,2020-10-08 00:00:00,944,72,false
+52,user_000052,[email protected],13715215128,Street 529, Block
C,City_11,Province_12,Country_US,253398,2020-02-06 00:00:00,118,68,false
+53,user_000053,[email protected],13268426575,Street 138, Block
B,City_2,Province_4,Country_JP,766008,2024-01-03 00:00:00,370,90,false
+54,user_000054,[email protected],13919702298,Street 657, Block
C,City_42,Province_18,Country_US,793847,2020-03-26 00:00:00,461,61,true
+55,user_000055,[email protected],13321343128,Street 86, Block
A,City_23,Province_1,Country_JP,808417,2025-11-08 00:00:00,655,98,true
+56,user_000056,[email protected],13647902427,Street 344, Block
B,City_20,Province_8,Country_EU,626439,2023-05-14 00:00:00,426,86,false
+57,user_000057,[email protected],13294848313,Street 297, Block
C,City_34,Province_7,Country_JP,723079,2021-04-01 00:00:00,430,80,false
+58,user_000058,[email protected],13716353156,Street 928, Block
B,City_50,Province_18,Country_JP,421411,2020-03-23 00:00:00,404,91,true
+59,user_000059,[email protected],13515676907,Street 796, Block
D,City_14,Province_10,Country_US,135071,2021-06-21 00:00:00,437,64,true
+60,user_000060,[email protected],13496395970,Street 844, Block
B,City_30,Province_12,Country_EU,684078,2021-11-23 00:00:00,757,89,true
+61,user_000061,[email protected],13948951149,Street 929, Block
B,City_2,Province_18,Country_EU,570849,2024-10-05 00:00:00,248,71,false
+62,user_000062,[email protected],13779476362,Street 9, Block
A,City_8,Province_18,Country_JP,384613,2023-02-28 00:00:00,18,86,true
+63,user_000063,[email protected],13218708673,Street 990, Block
A,City_19,Province_16,Country_EU,260582,2022-08-07 00:00:00,450,88,false
+64,user_000064,[email protected],13158911916,Street 613, Block
B,City_25,Province_19,Country_JP,922444,2024-06-28 00:00:00,549,99,true
+65,user_000065,[email protected],13973910531,Street 885, Block
C,City_40,Province_10,Country_JP,677529,2020-05-25 00:00:00,405,63,false
+66,user_000066,[email protected],13710315652,Street 849, Block
D,City_16,Province_15,Country_EU,420609,2021-04-18 00:00:00,838,65,true
+67,user_000067,[email protected],13932561889,Street 410, Block
D,City_33,Province_13,Country_EU,747460,2025-12-21 00:00:00,687,72,true
+68,user_000068,[email protected],13904143482,Street 338, Block
D,City_10,Province_19,Country_CN,286740,2024-09-14 00:00:00,145,100,false
+69,user_000069,[email protected],13419044781,Street 111, Block
D,City_8,Province_6,Country_US,697032,2024-02-04 00:00:00,699,75,true
+70,user_000070,[email protected],13844217507,Street 552, Block
B,City_28,Province_10,Country_EU,565936,2020-08-05 00:00:00,672,91,false
+71,user_000071,[email protected],13241727667,Street 692, Block
B,City_32,Province_18,Country_CN,843681,2020-11-15 00:00:00,207,84,false
+72,user_000072,[email protected],13393028013,Street 741, Block
C,City_31,Province_12,Country_JP,142070,2021-11-25 00:00:00,943,83,true
+73,user_000073,[email protected],13825962530,Street 553, Block
B,City_40,Province_17,Country_US,583437,2022-01-26 00:00:00,748,82,false
+74,user_000074,[email protected],13874607478,Street 451, Block
C,City_26,Province_18,Country_JP,377861,2021-05-24 00:00:00,838,90,true
+75,user_000075,[email protected],13472671799,Street 586, Block
A,City_8,Province_11,Country_EU,372742,2023-11-09 00:00:00,694,79,false
+76,user_000076,[email protected],13359279384,Street 364, Block
C,City_47,Province_15,Country_US,414555,2022-05-09 00:00:00,690,60,false
+77,user_000077,[email protected],13778565310,Street 380, Block
C,City_43,Province_15,Country_US,653604,2022-05-01 00:00:00,980,99,false
+78,user_000078,[email protected],13746938774,Street 178, Block
B,City_19,Province_16,Country_JP,327244,2022-09-05 00:00:00,330,93,false
+79,user_000079,[email protected],13494329363,Street 633, Block
D,City_6,Province_7,Country_EU,926366,2025-12-17 00:00:00,389,69,false
+80,user_000080,[email protected],13078081667,Street 406, Block
B,City_12,Province_7,Country_EU,845748,2020-06-03 00:00:00,11,91,false
+81,user_000081,[email protected],13657045398,Street 959, Block
A,City_1,Province_16,Country_CN,105318,2020-03-06 00:00:00,431,99,true
+82,user_000082,[email protected],13174027761,Street 509, Block
C,City_31,Province_10,Country_US,211734,2023-12-16 00:00:00,748,69,false
+83,user_000083,[email protected],13377956096,Street 739, Block
B,City_5,Province_8,Country_US,550099,2023-08-28 00:00:00,536,96,false
+84,user_000084,[email protected],13760446853,Street 728, Block
D,City_17,Province_12,Country_US,942947,2024-07-26 00:00:00,486,71,true
+85,user_000085,[email protected],13168766525,Street 72, Block
C,City_49,Province_9,Country_JP,804218,2024-01-01 00:00:00,324,80,false
+86,user_000086,[email protected],13313466629,Street 450, Block
A,City_2,Province_5,Country_US,944314,2025-10-04 00:00:00,24,77,true
+87,user_000087,[email protected],13808392774,Street 286, Block
A,City_39,Province_20,Country_CN,430870,2025-07-31 00:00:00,610,79,true
+88,user_000088,[email protected],13606416084,Street 303, Block
C,City_27,Province_20,Country_JP,551827,2021-05-29 00:00:00,265,79,false
+89,user_000089,[email protected],13076546124,Street 25, Block
A,City_35,Province_5,Country_CN,692518,2023-01-26 00:00:00,582,95,true
+90,user_000090,[email protected],13949230728,Street 423, Block
C,City_9,Province_11,Country_CN,332131,2022-10-07 00:00:00,806,86,true
+91,user_000091,[email protected],13418876810,Street 879, Block
D,City_33,Province_19,Country_JP,660234,2024-05-30 00:00:00,687,63,true
+92,user_000092,[email protected],13262177119,Street 582, Block
B,City_34,Province_5,Country_CN,413912,2020-05-26 00:00:00,659,78,true
+93,user_000093,[email protected],13007787378,Street 148, Block
D,City_4,Province_17,Country_US,282234,2025-11-29 00:00:00,370,94,false
+94,user_000094,[email protected],13758851386,Street 648, Block
D,City_8,Province_13,Country_CN,273036,2021-05-03 00:00:00,424,70,false
+95,user_000095,[email protected],13959198437,Street 698, Block
C,City_8,Province_12,Country_US,225005,2023-01-19 00:00:00,978,82,true
+96,user_000096,[email protected],13569515572,Street 748, Block
C,City_50,Province_12,Country_US,211430,2022-01-13 00:00:00,411,67,false
+97,user_000097,[email protected],13258643151,Street 378, Block
C,City_49,Province_9,Country_JP,762058,2023-07-22 00:00:00,156,62,true
+98,user_000098,[email protected],13815088832,Street 44, Block
C,City_48,Province_11,Country_JP,276141,2024-05-25 00:00:00,309,89,true
+99,user_000099,[email protected],13353229939,Street 590, Block
C,City_15,Province_8,Country_CN,945966,2021-06-08 00:00:00,46,65,false
+100,user_000100,[email protected],13507984044,Street 21, Block
D,City_3,Province_14,Country_US,750228,2020-08-04 00:00:00,494,70,true
\ No newline at end of file