Skip to content

Commit fb0b812

Browse files
authored
Add Guess plugin (#7)
* Guess機能の復活 * Guess機能のJava実装と、CI修正 * spotless apply
1 parent fc09e40 commit fb0b812

7 files changed

Lines changed: 335 additions & 17 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ jobs:
1313
name: Test and Check
1414
runs-on: ubuntu-latest
1515
steps:
16-
- uses: actions/checkout@v4
16+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
1717

1818
- name: Set up JDK 1.8
19-
uses: actions/setup-java@v4
19+
uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4.8.0
2020
with:
2121
java-version: '8'
2222
distribution: 'temurin'
2323

2424
- name: Setup Gradle
25-
uses: gradle/actions/setup-gradle@v3
25+
uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
2626

2727
- name: Run Spotless Check
2828
run: ./gradlew spotlessCheck
@@ -32,7 +32,7 @@ jobs:
3232

3333
- name: Upload Test Results
3434
if: always()
35-
uses: actions/upload-artifact@v4
35+
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
3636
with:
3737
name: test-results
3838
path: build/reports/tests/test/

.github/workflows/gem-push.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ jobs:
1616
packages: write
1717
contents: read
1818
steps:
19-
- uses: actions/checkout@v4
19+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
2020
- name: Set up Java8
21-
uses: actions/setup-java@v4
21+
uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4.8.0
2222
with:
2323
distribution: "temurin"
2424
java-version: "8"
2525
- name: push gem
26-
uses: trocco-io/push-gem-to-gpr-action@v2
26+
uses: trocco-io/push-gem-to-gpr-action@25bf27a8aab8b3c95e2aa19189d9815942fdfabf # v2
2727
with:
2828
language: java
2929
gem-path: "./build/gems/*.gem"

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ gem {
3737
summary = "Jsonl parser plugin for Embulk"
3838
homepage = "https://github.com/shun0102/embulk-parser-jsonl"
3939
licenses = ["MIT"]
40+
from("lib") {
41+
into("lib")
42+
}
4043
}
4144

4245
dependencies {
@@ -47,6 +50,7 @@ dependencies {
4750
implementation "org.embulk:embulk-util-json:0.3.0"
4851
implementation "org.embulk:embulk-util-timestamp:0.2.2"
4952
implementation "org.embulk:embulk-util-text:0.1.1"
53+
implementation "org.embulk:embulk-util-guess:0.3.0"
5054

5155
testImplementation "junit:junit:4.+"
5256
testImplementation "org.embulk:embulk-api:0.10.43"

gradle.lockfile

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,69 @@
11
# This is a Gradle generated file for dependency locking.
22
# Manual edits can break the build and are not advised.
33
# This file is expected to be part of source control.
4+
com.fasterxml.jackson.core:jackson-annotations:2.16.2=testCompileClasspath,testRuntimeClasspath
45
com.fasterxml.jackson.core:jackson-annotations:2.6.7=compileClasspath,runtimeClasspath
6+
com.fasterxml.jackson.core:jackson-core:2.16.2=testCompileClasspath,testRuntimeClasspath
57
com.fasterxml.jackson.core:jackson-core:2.6.7=compileClasspath,runtimeClasspath
8+
com.fasterxml.jackson.core:jackson-databind:2.16.2=testCompileClasspath,testRuntimeClasspath
69
com.fasterxml.jackson.core:jackson-databind:2.6.7.5=compileClasspath,runtimeClasspath
10+
com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.16.2=testCompileClasspath,testRuntimeClasspath
711
com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.6.7=compileClasspath,runtimeClasspath
8-
javax.validation:validation-api:1.1.0.Final=compileClasspath,runtimeClasspath
9-
org.embulk:embulk-api:0.10.43=compileClasspath
10-
org.embulk:embulk-spi:0.11=compileClasspath
11-
org.embulk:embulk-util-config:0.3.4=compileClasspath,runtimeClasspath
12-
org.embulk:embulk-util-file:0.1.3=compileClasspath,runtimeClasspath
13-
org.embulk:embulk-util-json:0.3.0=compileClasspath,runtimeClasspath
12+
com.fasterxml.jackson:jackson-bom:2.16.2=testCompileClasspath,testRuntimeClasspath
13+
commons-cli:commons-cli:1.7.0=testCompileClasspath,testRuntimeClasspath
14+
commons-codec:commons-codec:1.17.0=testCompileClasspath,testRuntimeClasspath
15+
io.netty:netty-buffer:4.1.109.Final=testCompileClasspath,testRuntimeClasspath
16+
io.netty:netty-common:4.1.109.Final=testCompileClasspath,testRuntimeClasspath
17+
javax.inject:javax.inject:1=testCompileClasspath,testRuntimeClasspath
18+
javax.validation:validation-api:1.1.0.Final=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
19+
junit:junit:4.13.2=testCompileClasspath,testRuntimeClasspath
20+
org.apache.commons:commons-lang3:3.14.0=testCompileClasspath,testRuntimeClasspath
21+
org.apache.httpcomponents:httpclient:4.5.14=testCompileClasspath,testRuntimeClasspath
22+
org.apache.httpcomponents:httpcore:4.4.16=testCompileClasspath,testRuntimeClasspath
23+
org.apache.maven.resolver:maven-resolver-api:1.9.20=testCompileClasspath,testRuntimeClasspath
24+
org.apache.maven.resolver:maven-resolver-connector-basic:1.9.20=testCompileClasspath,testRuntimeClasspath
25+
org.apache.maven.resolver:maven-resolver-impl:1.9.20=testCompileClasspath,testRuntimeClasspath
26+
org.apache.maven.resolver:maven-resolver-named-locks:1.9.20=testCompileClasspath,testRuntimeClasspath
27+
org.apache.maven.resolver:maven-resolver-spi:1.9.20=testCompileClasspath,testRuntimeClasspath
28+
org.apache.maven.resolver:maven-resolver-supplier:1.9.20=testCompileClasspath,testRuntimeClasspath
29+
org.apache.maven.resolver:maven-resolver-transport-file:1.9.20=testCompileClasspath,testRuntimeClasspath
30+
org.apache.maven.resolver:maven-resolver-transport-http:1.9.20=testCompileClasspath,testRuntimeClasspath
31+
org.apache.maven.resolver:maven-resolver-util:1.9.20=testCompileClasspath,testRuntimeClasspath
32+
org.apache.maven:maven-artifact:3.9.6=testCompileClasspath,testRuntimeClasspath
33+
org.apache.maven:maven-builder-support:3.9.6=testCompileClasspath,testRuntimeClasspath
34+
org.apache.maven:maven-model-builder:3.9.6=testCompileClasspath,testRuntimeClasspath
35+
org.apache.maven:maven-model:3.9.6=testCompileClasspath,testRuntimeClasspath
36+
org.apache.maven:maven-repository-metadata:3.9.6=testCompileClasspath,testRuntimeClasspath
37+
org.apache.maven:maven-resolver-provider:3.9.6=testCompileClasspath,testRuntimeClasspath
38+
org.codehaus.plexus:plexus-interpolation:1.26=testCompileClasspath,testRuntimeClasspath
39+
org.codehaus.plexus:plexus-utils:3.5.1=testCompileClasspath,testRuntimeClasspath
40+
org.eclipse.sisu:org.eclipse.sisu.inject:0.9.0.M2=testCompileClasspath,testRuntimeClasspath
41+
org.embulk:embulk-api:0.10.43=compileClasspath,testCompileClasspath,testRuntimeClasspath
42+
org.embulk:embulk-core:0.11.5=testCompileClasspath,testRuntimeClasspath
43+
org.embulk:embulk-deps:0.11.5=testCompileClasspath,testRuntimeClasspath
44+
org.embulk:embulk-junit4:0.11.5=testCompileClasspath,testRuntimeClasspath
45+
org.embulk:embulk-spi:0.11=compileClasspath,testCompileClasspath,testRuntimeClasspath
46+
org.embulk:embulk-util-config:0.3.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
47+
org.embulk:embulk-util-file:0.1.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
48+
org.embulk:embulk-util-guess:0.3.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
49+
org.embulk:embulk-util-json:0.3.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
1450
org.embulk:embulk-util-rubytime:0.3.3=compileClasspath,runtimeClasspath
15-
org.embulk:embulk-util-text:0.1.1=compileClasspath,runtimeClasspath
51+
org.embulk:embulk-util-rubytime:0.4.0=testCompileClasspath,testRuntimeClasspath
52+
org.embulk:embulk-util-text:0.1.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
1653
org.embulk:embulk-util-timestamp:0.2.2=compileClasspath,runtimeClasspath
17-
org.msgpack:msgpack-core:0.8.24=compileClasspath
18-
org.slf4j:slf4j-api:2.0.7=compileClasspath
19-
empty=
54+
org.embulk:embulk-util-timestamp:0.3.0=testCompileClasspath,testRuntimeClasspath
55+
org.hamcrest:hamcrest-core:1.3=testCompileClasspath,testRuntimeClasspath
56+
org.hamcrest:hamcrest-library:1.3=testCompileClasspath,testRuntimeClasspath
57+
org.jacoco:org.jacoco.agent:0.8.9=jacocoAgent,jacocoAnt
58+
org.jacoco:org.jacoco.ant:0.8.9=jacocoAnt
59+
org.jacoco:org.jacoco.core:0.8.9=jacocoAnt
60+
org.jacoco:org.jacoco.report:0.8.9=jacocoAnt
61+
org.msgpack:msgpack-core:0.8.24=compileClasspath,testCompileClasspath,testRuntimeClasspath
62+
org.ow2.asm:asm-commons:9.5=jacocoAnt
63+
org.ow2.asm:asm-tree:9.5=jacocoAnt
64+
org.ow2.asm:asm:9.5=jacocoAnt
65+
org.slf4j:jcl-over-slf4j:2.0.13=testCompileClasspath,testRuntimeClasspath
66+
org.slf4j:slf4j-api:2.0.13=testRuntimeClasspath
67+
org.slf4j:slf4j-api:2.0.7=compileClasspath,testCompileClasspath
68+
org.yaml:snakeyaml:2.2=testCompileClasspath,testRuntimeClasspath
69+
empty=annotationProcessor,testAnnotationProcessor

lib/embulk/guess/jsonl.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Register Java GuessPlugin directly
2+
Embulk::JavaPlugin.register_guess(
3+
"jsonl", "org.embulk.parser.jsonl.JsonlGuessPlugin",
4+
File.expand_path("../../../../classpath", __FILE__)
5+
)
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package org.embulk.parser.jsonl;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import java.io.IOException;
6+
import java.nio.charset.Charset;
7+
import java.util.ArrayList;
8+
import java.util.LinkedHashMap;
9+
import java.util.List;
10+
import java.util.Map;
11+
import org.embulk.config.ConfigDiff;
12+
import org.embulk.config.ConfigSource;
13+
import org.embulk.spi.Buffer;
14+
import org.embulk.spi.GuessPlugin;
15+
import org.embulk.util.config.ConfigMapperFactory;
16+
import org.embulk.util.guess.SchemaGuess;
17+
import org.slf4j.Logger;
18+
import org.slf4j.LoggerFactory;
19+
20+
public class JsonlGuessPlugin implements GuessPlugin {
21+
private static final Logger log = LoggerFactory.getLogger(JsonlGuessPlugin.class);
22+
private static final ConfigMapperFactory CONFIG_MAPPER_FACTORY =
23+
ConfigMapperFactory.builder().addDefaultModules().build();
24+
25+
@Override
26+
@SuppressWarnings("deprecation")
27+
public ConfigDiff guess(ConfigSource config, Buffer sample) {
28+
try {
29+
// Check if parser type is jsonl
30+
ConfigSource parserConfig = config.getNested("parser");
31+
String parserType = parserConfig.get(String.class, "type", null);
32+
if (!"jsonl".equals(parserType)) {
33+
return CONFIG_MAPPER_FACTORY.newConfigDiff();
34+
}
35+
36+
// Get configuration
37+
String newlineType = parserConfig.get(String.class, "newline", "CRLF");
38+
String charset = parserConfig.get(String.class, "charset", "UTF-8");
39+
int minRowsForGuess = parserConfig.get(Integer.class, "min_rows_for_guess", 4);
40+
41+
// Convert newline type to character
42+
String newlineChar = newlineCharacter(newlineType);
43+
44+
// Parse sample data as texts
45+
byte[] bytes = sample.array();
46+
String sampleText = new String(bytes, Charset.forName(charset));
47+
48+
// Parse JSONL lines
49+
ObjectMapper objectMapper = new ObjectMapper();
50+
List<LinkedHashMap<String, Object>> rows = new ArrayList<>();
51+
52+
for (String line : sampleText.split(newlineChar, -1)) {
53+
if (line.trim().isEmpty()) {
54+
continue;
55+
}
56+
57+
try {
58+
JsonNode jsonNode = objectMapper.readTree(line);
59+
if (!jsonNode.isObject()) {
60+
log.warn("Skipped non-object JSON value: {}", line);
61+
continue;
62+
}
63+
64+
LinkedHashMap<String, Object> row = new LinkedHashMap<>();
65+
jsonNode
66+
.fields()
67+
.forEachRemaining(
68+
entry -> {
69+
row.put(entry.getKey(), convertJsonNodeToObject(entry.getValue()));
70+
});
71+
rows.add(row);
72+
} catch (IOException e) {
73+
log.warn("Failed to parse line: {}", line, e);
74+
}
75+
}
76+
77+
// Check minimum rows
78+
if (rows.size() < minRowsForGuess) {
79+
log.info("Not enough rows for guess: {} < {}", rows.size(), minRowsForGuess);
80+
return CONFIG_MAPPER_FACTORY.newConfigDiff();
81+
}
82+
83+
if (rows.isEmpty()) {
84+
throw new RuntimeException("SchemaGuess Can't guess schema from no records");
85+
}
86+
87+
// Guess schema using SchemaGuess
88+
SchemaGuess schemaGuess = SchemaGuess.of(CONFIG_MAPPER_FACTORY);
89+
List<ConfigDiff> guessedColumns = schemaGuess.fromLinkedHashMapRecords(rows);
90+
91+
// Convert ConfigDiff to Map for easier manipulation
92+
List<Map<String, Object>> columns = new ArrayList<>();
93+
for (ConfigDiff columnDiff : guessedColumns) {
94+
Map<String, Object> columnConfig = new LinkedHashMap<>();
95+
columnConfig.put("name", columnDiff.get(String.class, "name"));
96+
columnConfig.put("type", columnDiff.get(String.class, "type"));
97+
String format = columnDiff.get(String.class, "format", null);
98+
if (format != null) {
99+
columnConfig.put("format", format);
100+
}
101+
columns.add(columnConfig);
102+
}
103+
104+
// Build result
105+
ConfigDiff result = CONFIG_MAPPER_FACTORY.newConfigDiff();
106+
result.setNested(
107+
"parser",
108+
CONFIG_MAPPER_FACTORY.newConfigDiff().set("type", "jsonl").set("columns", columns));
109+
110+
return result;
111+
} catch (Exception e) {
112+
log.error("Failed to guess schema", e);
113+
return CONFIG_MAPPER_FACTORY.newConfigDiff();
114+
}
115+
}
116+
117+
private Object convertJsonNodeToObject(JsonNode node) {
118+
if (node.isNull()) {
119+
return null;
120+
} else if (node.isBoolean()) {
121+
return node.asBoolean();
122+
} else if (node.isLong()) {
123+
return node.asLong();
124+
} else if (node.isDouble() || node.isFloat()) {
125+
return node.asDouble();
126+
} else if (node.isTextual()) {
127+
return node.asText();
128+
} else if (node.isArray() || node.isObject()) {
129+
return node.toString();
130+
} else {
131+
return node.asText();
132+
}
133+
}
134+
135+
private String newlineCharacter(String newlineType) {
136+
switch (newlineType) {
137+
case "CRLF":
138+
return "\r\n";
139+
case "LF":
140+
return "\n";
141+
case "CR":
142+
return "\r";
143+
default:
144+
return "\r\n";
145+
}
146+
}
147+
}

0 commit comments

Comments
 (0)