Skip to content

Commit 39f036a

Browse files
🎉 New Destination: Yellowbrick (#35775)
Co-authored-by: Marcos Marx <[email protected]>
1 parent ef3c765 commit 39f036a

File tree

56 files changed

+1849
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1849
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Yellowbrick Destination Connector Bootstrap
2+
3+
Yellowbrick is a highly efficient and elastically scalable data warehouse that runs on Kubernetes in all major public clouds and on-premises.
4+
5+
Yellowbrick connector produces the standard Airbyte outputs using `_airbyte_raw_*` tables storing the JSON blob data first. Afterward, these are transformed and normalized into separate tables, potentially "exploding" nested streams into their own tables if [basic normalization](https://docs.airbyte.io/understanding-airbyte/basic-normalization) is configured.
6+
7+
See [this](https://docs.airbyte.io/integrations/destinations/yellowbrick) link for more information about the connector.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Destination Yellowbrick
2+
3+
This is the repository for the Yellowbrick destination connector in Java.
4+
For information about how to use this connector within Airbyte, see [the User Documentation](https://docs.airbyte.com/integrations/destinations/yellowbrick).
5+
6+
## Local development
7+
8+
#### Building via Gradle
9+
From the Airbyte repository root, run:
10+
```
11+
./gradlew :airbyte-integrations:connectors:destination-yellowbrick:build
12+
```
13+
14+
#### Create credentials
15+
**If you are a community contributor**, generate the necessary credentials and place them in `secrets/config.json` conforming to the spec file in `src/main/resources/spec.json`.
16+
Note that the `secrets` directory is git-ignored by default, so there is no danger of accidentally checking in sensitive information.
17+
18+
**If you are an Airbyte core member**, follow the [instructions](https://docs.airbyte.com/connector-development#using-credentials-in-ci) to set up the credentials.
19+
20+
### Locally running the connector docker image
21+
22+
#### Build
23+
Build the connector image via Gradle:
24+
```
25+
./gradlew :airbyte-integrations:connectors:destination-yellowbrick:airbyteDocker
26+
```
27+
When building via Gradle, the docker image name and tag, respectively, are the values of the `io.airbyte.name` and `io.airbyte.version` `LABEL`s in
28+
the Dockerfile.
29+
30+
#### Run
31+
Then run any of the connector commands as follows:
32+
```
33+
docker run --rm airbyte/destination-yellowbrick:dev spec
34+
docker run --rm -v $(pwd)/secrets:/secrets airbyte/destination-yellowbrick:dev check --config /secrets/config.json
35+
docker run --rm -v $(pwd)/secrets:/secrets airbyte/destination-yellowbrick:dev discover --config /secrets/config.json
36+
docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/destination-yellowbrick:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json
37+
```
38+
39+
## Testing
40+
We use `JUnit` for Java tests.
41+
42+
### Unit and Integration Tests
43+
Place unit tests under `src/test/io/airbyte/integrations/destinations/yellowbrick`.
44+
45+
#### Acceptance Tests
46+
Airbyte has a standard test suite that all destination connectors must pass. Implement the `TODO`s in
47+
`src/test-integration/java/io/airbyte/integrations/destinations/yellowbrickDestinationAcceptanceTest.java`.
48+
49+
### Using gradle to run tests
50+
All commands should be run from airbyte project root.
51+
To run unit tests:
52+
```
53+
./gradlew :airbyte-integrations:connectors:destination-yellowbrick:unitTest
54+
```
55+
To run acceptance and custom integration tests:
56+
```
57+
./gradlew :airbyte-integrations:connectors:destination-yellowbrick:integrationTest
58+
```
59+
60+
## Dependency Management
61+
62+
### Publishing a new version of the connector
63+
You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what?
64+
1. Make sure your changes are passing unit and integration tests.
65+
1. Bump the connector version in `Dockerfile` -- just increment the value of the `LABEL io.airbyte.version` appropriately (we use [SemVer](https://semver.org/)).
66+
1. Create a Pull Request.
67+
1. Pat yourself on the back for being an awesome contributor.
68+
1. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
plugins {
2+
id 'airbyte-java-connector'
3+
id 'org.jetbrains.kotlin.jvm' version '1.9.23'
4+
}
5+
6+
airbyteJavaConnector {
7+
cdkVersionRequired = '0.27.7'
8+
features = ['db-destinations', 'datastore-postgres', 'typing-deduping']
9+
useLocalCdk = false
10+
}
11+
12+
application {
13+
mainClass = 'io.airbyte.integrations.destination.yellowbrick.YellowbrickDestination'
14+
applicationDefaultJvmArgs = ['-XX:+ExitOnOutOfMemoryError', '-XX:MaxRAMPercentage=75.0']
15+
}
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
data:
2+
ab_internal:
3+
ql: 200
4+
sl: 100
5+
connectorSubtype: database
6+
connectorType: destination
7+
definitionId: 1f7bac7e-53ff-4e0b-b6df-b74aa85cf703
8+
dockerImageTag: 0.0.1
9+
dockerRepository: airbyte/destination-yellowbrick
10+
documentationUrl: https://docs.airbyte.com/integrations/destinations/yellowbrick
11+
githubIssueLabel: destination-yellowbrick
12+
icon: yellowbrick.svg
13+
license: ELv2
14+
name: Yellowbrick
15+
registries:
16+
cloud:
17+
dockerRepository: airbyte/destination-yellowbrick
18+
enabled: true
19+
oss:
20+
enabled: true
21+
releaseStage: alpha
22+
supportLevel: community
23+
supportsDbt: false
24+
tags:
25+
- language:java
26+
metadataSpecVersion: "1.0"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3+
*/
4+
5+
package io.airbyte.integrations.destination.yellowbrick;
6+
7+
import static io.airbyte.cdk.integrations.util.PostgresSslConnectionUtils.DISABLE;
8+
import static io.airbyte.cdk.integrations.util.PostgresSslConnectionUtils.PARAM_MODE;
9+
import static io.airbyte.cdk.integrations.util.PostgresSslConnectionUtils.PARAM_SSL;
10+
import static io.airbyte.cdk.integrations.util.PostgresSslConnectionUtils.PARAM_SSL_MODE;
11+
import static io.airbyte.cdk.integrations.util.PostgresSslConnectionUtils.obtainConnectionOptions;
12+
13+
import com.fasterxml.jackson.databind.JsonNode;
14+
import com.google.common.collect.ImmutableMap;
15+
import io.airbyte.cdk.db.factory.DatabaseDriver;
16+
import io.airbyte.cdk.db.jdbc.JdbcDatabase;
17+
import io.airbyte.cdk.db.jdbc.JdbcUtils;
18+
import io.airbyte.cdk.integrations.base.*;
19+
import io.airbyte.cdk.integrations.base.ssh.SshWrappedDestination;
20+
import io.airbyte.cdk.integrations.destination.async.deser.StreamAwareDataTransformer;
21+
import io.airbyte.cdk.integrations.destination.jdbc.AbstractJdbcDestination;
22+
import io.airbyte.cdk.integrations.destination.jdbc.typing_deduping.JdbcDestinationHandler;
23+
import io.airbyte.cdk.integrations.destination.jdbc.typing_deduping.JdbcSqlGenerator;
24+
import io.airbyte.commons.json.Jsons;
25+
import io.airbyte.integrations.base.destination.typing_deduping.DestinationHandler;
26+
import io.airbyte.integrations.base.destination.typing_deduping.ParsedCatalog;
27+
import io.airbyte.integrations.base.destination.typing_deduping.SqlGenerator;
28+
import io.airbyte.integrations.base.destination.typing_deduping.migrators.Migration;
29+
import io.airbyte.integrations.destination.yellowbrick.typing_deduping.YellowbrickDataTransformer;
30+
import io.airbyte.integrations.destination.yellowbrick.typing_deduping.YellowbrickDestinationHandler;
31+
import io.airbyte.integrations.destination.yellowbrick.typing_deduping.YellowbrickSqlGenerator;
32+
import io.airbyte.integrations.destination.yellowbrick.typing_deduping.YellowbrickState;
33+
import java.io.UnsupportedEncodingException;
34+
import java.net.URLEncoder;
35+
import java.util.HashMap;
36+
import java.util.List;
37+
import java.util.Map;
38+
import java.util.Optional;
39+
import org.slf4j.Logger;
40+
import org.slf4j.LoggerFactory;
41+
42+
public class YellowbrickDestination extends AbstractJdbcDestination<YellowbrickState> implements Destination {
43+
44+
private static final Logger LOGGER = LoggerFactory.getLogger(YellowbrickDestination.class);
45+
46+
public static final String DRIVER_CLASS = DatabaseDriver.POSTGRESQL.getDriverClassName();
47+
48+
public static Destination sshWrappedDestination() {
49+
return new SshWrappedDestination(new YellowbrickDestination(), JdbcUtils.HOST_LIST_KEY, JdbcUtils.PORT_LIST_KEY);
50+
}
51+
52+
public YellowbrickDestination() {
53+
super(DRIVER_CLASS, new YellowbrickSQLNameTransformer(), new YellowbrickSqlOperations());
54+
}
55+
56+
@Override
57+
protected Map<String, String> getDefaultConnectionProperties(final JsonNode config) {
58+
final Map<String, String> additionalParameters = new HashMap<>();
59+
if (!config.has(PARAM_SSL) || config.get(PARAM_SSL).asBoolean()) {
60+
if (config.has(PARAM_SSL_MODE)) {
61+
if (DISABLE.equals(config.get(PARAM_SSL_MODE).get(PARAM_MODE).asText())) {
62+
additionalParameters.put("sslmode", DISABLE);
63+
} else {
64+
additionalParameters.putAll(obtainConnectionOptions(config.get(PARAM_SSL_MODE)));
65+
}
66+
} else {
67+
additionalParameters.put(JdbcUtils.SSL_KEY, "true");
68+
additionalParameters.put("sslmode", "require");
69+
}
70+
}
71+
return additionalParameters;
72+
}
73+
74+
@Override
75+
public JsonNode toJdbcConfig(final JsonNode config) {
76+
final String schema = Optional.ofNullable(config.get(JdbcUtils.SCHEMA_KEY)).map(JsonNode::asText).orElse("public");
77+
78+
String encodedDatabase = config.get(JdbcUtils.DATABASE_KEY).asText();
79+
if (encodedDatabase != null) {
80+
try {
81+
encodedDatabase = URLEncoder.encode(encodedDatabase, "UTF-8");
82+
} catch (final UnsupportedEncodingException e) {
83+
// Should never happen
84+
e.printStackTrace();
85+
}
86+
}
87+
final String jdbcUrl = String.format("jdbc:postgresql://%s:%s/%s?",
88+
config.get(JdbcUtils.HOST_KEY).asText(),
89+
config.get(JdbcUtils.PORT_KEY).asText(),
90+
encodedDatabase);
91+
92+
final ImmutableMap.Builder<Object, Object> configBuilder = ImmutableMap.builder()
93+
.put(JdbcUtils.USERNAME_KEY, config.get(JdbcUtils.USERNAME_KEY).asText())
94+
.put(JdbcUtils.JDBC_URL_KEY, jdbcUrl)
95+
.put(JdbcUtils.SCHEMA_KEY, schema);
96+
97+
if (config.has(JdbcUtils.PASSWORD_KEY)) {
98+
configBuilder.put(JdbcUtils.PASSWORD_KEY, config.get(JdbcUtils.PASSWORD_KEY).asText());
99+
}
100+
101+
if (config.has(JdbcUtils.JDBC_URL_PARAMS_KEY)) {
102+
configBuilder.put(JdbcUtils.JDBC_URL_PARAMS_KEY, config.get(JdbcUtils.JDBC_URL_PARAMS_KEY).asText());
103+
}
104+
105+
return Jsons.jsonNode(configBuilder.build());
106+
}
107+
108+
@Override
109+
protected JdbcDestinationHandler<YellowbrickState> getDestinationHandler(String databaseName, JdbcDatabase database, String rawTableSchema) {
110+
return new YellowbrickDestinationHandler(databaseName, database, rawTableSchema);
111+
}
112+
113+
@Override
114+
protected JdbcSqlGenerator getSqlGenerator() {
115+
return new YellowbrickSqlGenerator(new YellowbrickSQLNameTransformer());
116+
}
117+
118+
@Override
119+
protected StreamAwareDataTransformer getDataTransformer(ParsedCatalog parsedCatalog, String defaultNamespace) {
120+
return new YellowbrickDataTransformer();
121+
}
122+
123+
@Override
124+
public boolean isV2Destination() {
125+
return true;
126+
}
127+
128+
@Override
129+
protected List<Migration<YellowbrickState>> getMigrations(JdbcDatabase database,
130+
String databaseName,
131+
SqlGenerator sqlGenerator,
132+
DestinationHandler<YellowbrickState> destinationHandler) {
133+
return List.of();
134+
}
135+
136+
public static void main(final String[] args) throws Exception {
137+
final Destination destination = YellowbrickDestination.sshWrappedDestination();
138+
LOGGER.info("starting destination: {}", YellowbrickDestination.class);
139+
new IntegrationRunner(destination).run(args);
140+
LOGGER.info("completed destination: {}", YellowbrickDestination.class);
141+
}
142+
143+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3+
*/
4+
5+
package io.airbyte.integrations.destination.yellowbrick;
6+
7+
import io.airbyte.cdk.integrations.destination.StandardNameTransformer;
8+
9+
public class YellowbrickSQLNameTransformer extends StandardNameTransformer {
10+
11+
@Override
12+
public String applyDefaultCase(final String input) {
13+
return input.toLowerCase();
14+
}
15+
16+
}

0 commit comments

Comments
 (0)