Skip to content

Commit

Permalink
DynamoDB external datastore support library (#543)
Browse files Browse the repository at this point in the history
* DynamoDB external datastore support library (Issue 69246)

* Add support for querying tables

* Add support for CRUD operations

* Fix error with blank password and local url. Fix lint errors

* Allow datetime fields to only contain the date part

* Add missing implementation for getDate/1

* Add special case for queries that filter with an empty string key

* Add support for binary streams

* Fix NullPointerException when trying to get a binary stream from a record which does not have one

* Add support for blobs, when duplicate/when none semantics. Fix datetimes

* add 'as' method to cast a query to its derived type

* Improve Query/Scan inference. Fix lint errors

* Fix datetime fields. Fix update queries

* Fix possible input types for Date and DateTime in toAttributeValue

* Change DateTime->String conversion. It was shifting offsets twice to reach UTC

* Simplify empty datetime parameters so there is no need manage java.util.Date and java.sql.Timestamp separaterly

* Update build workflow - #596

Co-authored-by: José Echagüe <[email protected]>
Co-authored-by: Gonzalo <[email protected]>
  • Loading branch information
3 people authored Sep 1, 2022
1 parent 9330d53 commit d42bbca
Show file tree
Hide file tree
Showing 19 changed files with 1,452 additions and 37 deletions.
58 changes: 58 additions & 0 deletions gxdynamodb/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.genexus</groupId>
<artifactId>parent</artifactId>
<version>${revision}${changelist}</version>
</parent>

<artifactId>gxdynamodb</artifactId>
<name>GeneXus DynamoDB</name>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>2.17.151</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>gxclassR</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.genexus</groupId>
<artifactId>gxcommon</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>

<build>
<finalName>gxdynamodb</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.genexus.db.dynamodb;

import com.genexus.CommonUtil;
import com.genexus.db.ServiceCursorBase;
import com.genexus.db.driver.GXConnection;
import com.genexus.db.driver.GXPreparedStatement;
import com.genexus.db.service.GXType;
import com.genexus.db.service.IQuery;
import com.genexus.db.service.ServiceDataStoreHelper;

import java.sql.Date;
import java.sql.Timestamp;

public class DataStoreHelperDynamoDB extends ServiceDataStoreHelper
{
public DynamoQuery newQuery()
{
return new DynamoQuery(this);
}
public DynamoQuery newScan()
{
return new DynamoScan(this);
}

public DynamoDBMap Map(String name)
{
return new DynamoDBMap(name);
}

public Object empty(GXType gxtype)
{
switch(gxtype)
{
case Number:
case Int16:
case Int32:
case Int64: return 0;
case Date: return new Date(CommonUtil.nullDate().getTime());
case DateTime:
case DateTime2: return new Timestamp(CommonUtil.nullDate().getTime());
case Byte:
case NChar:
case NClob:
case NVarChar:
case Char:
case LongVarChar:
case Clob:
case VarChar:
case Raw:
case Blob:
case NText:
case Text:
case Image:
case UniqueIdentifier:
case Xml:
case DateAsChar: return "";
case Boolean: return false;
case Decimal: return 0f;

case Geography:
case Geopoint:
case Geoline:
case Geopolygon:

case Undefined:
default: return null;
}
}

@Override
public GXPreparedStatement getPreparedStatement(GXConnection con, IQuery query, ServiceCursorBase cursor, int cursorNum, boolean currentOf, Object[] parms)
{
return new GXPreparedStatement(new DynamoDBPreparedStatement(con.getJDBCConnection(), (DynamoQuery)query, cursor, parms, con), con, con.getHandle(), "", cursor.getCursorId(), currentOf);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package com.genexus.db.dynamodb;

import com.genexus.db.service.ServiceConnection;
import org.apache.commons.lang.StringUtils;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder;

import java.net.URI;
import java.sql.ResultSet;
import java.util.Enumeration;
import java.util.Properties;
import java.util.concurrent.Executor;

public class DynamoDBConnection extends ServiceConnection
{
private static final String GXDYNAMODB_VERSION = "1.0";
private static final String GXDYNAMODB_PRODUCT_NAME = "DynamoDB";

private static final String CLIENT_ID = "user";
private static final String CLIENT_SECRET = "password";
private static final String REGION = "region";
private static final String LOCAL_URL = "localurl";


DynamoDbClient mDynamoDB;
Region mRegion = Region.US_EAST_1;

public DynamoDBConnection(String connUrl, Properties initialConnProps)
{
super(connUrl, initialConnProps); // After initialization use props variable from super class to manage properties
initializeDBConnection(connUrl);
}

private void initializeDBConnection(String connUrl)
{
String mLocalUrl = null, mClientId = null, mClientSecret = null;
for(Enumeration<Object> keys = props.keys(); keys.hasMoreElements(); )
{
String key = (String)keys.nextElement();
String value = props.getProperty(key, key);
switch(key.toLowerCase())
{
case LOCAL_URL: mLocalUrl = value; break;
case CLIENT_ID: mClientId = value; break;
case CLIENT_SECRET: mClientSecret = value; break;
case REGION: mRegion = Region.of(value); break;
default: break;
}
}
DynamoDbClientBuilder builder = DynamoDbClient.builder().region(mRegion);
if(mLocalUrl != null)
builder = builder.endpointOverride(URI.create(mLocalUrl));
if(StringUtils.isNotEmpty(mClientId) &&
StringUtils.isNotEmpty(mClientSecret))
{
AwsBasicCredentials mCredentials = AwsBasicCredentials.create(mClientId, mClientSecret);
builder = builder.credentialsProvider(StaticCredentialsProvider.create(mCredentials));
}
mDynamoDB = builder.build();
}

//----------------------------------------------------------------------------------------------------

@Override
public void close()
{
mDynamoDB.close();
mDynamoDB = null;
}

@Override
public boolean isClosed()
{
return mDynamoDB != null;
}

//----------------------------------------------------------------------------------------------------
@Override
public String getDatabaseProductName()
{
return GXDYNAMODB_PRODUCT_NAME;
}

@Override
public String getDatabaseProductVersion()
{
return "";
}

@Override
public String getDriverName()
{
return mDynamoDB.getClass().getName();
}

@Override
public String getDriverVersion()
{
return String.format("%s/%s", mDynamoDB.serviceName(), GXDYNAMODB_VERSION);
}

// JDK8:
@Override
public void setSchema(String schema)
{
throw new UnsupportedOperationException("Not supported yet.");
}

@Override
public String getSchema()
{
throw new UnsupportedOperationException("Not supported yet.");
}

@Override
public void abort(Executor executor)
{
throw new UnsupportedOperationException("Not supported yet.");
}

@Override
public void setNetworkTimeout(Executor executor, int milliseconds)
{
throw new UnsupportedOperationException("Not supported yet.");
}

@Override
public int getNetworkTimeout()
{
throw new UnsupportedOperationException("Not supported yet.");
}

@Override
public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern)
{
throw new UnsupportedOperationException("Not supported yet.");
}

@Override
public boolean generatedKeyAlwaysReturned()
{
throw new UnsupportedOperationException("Not supported yet.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.genexus.db.dynamodb;

import java.sql.*;
import java.util.Properties;
import java.util.logging.Logger;

public class DynamoDBDriver implements Driver
{
private static final int MAJOR_VERSION = 1;
private static final int MINOR_VERSION = 0;
private static final String DRIVER_ID = "dynamodb:";

private static final DynamoDBDriver DYNAMODB_DRIVER;
static
{
DYNAMODB_DRIVER = new DynamoDBDriver();
try
{
DriverManager.registerDriver(DYNAMODB_DRIVER);
}catch(SQLException e)
{
e.printStackTrace();
}
}

public DynamoDBDriver()
{
}

@Override
public Connection connect(String url, Properties info)
{
if(!acceptsURL(url))
return null;
return new DynamoDBConnection(url.substring(DRIVER_ID.length()), info);
}

@Override
public boolean acceptsURL(String url)
{
return url.startsWith(DRIVER_ID);
}

@Override
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info)
{
return new DriverPropertyInfo[0];
}

@Override
public int getMajorVersion()
{
return MAJOR_VERSION;
}

@Override
public int getMinorVersion()
{
return MINOR_VERSION;
}

@Override
public boolean jdbcCompliant()
{
return false;
}

@Override
public Logger getParentLogger()
{
throw new UnsupportedOperationException("Not supported yet.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.genexus.db.dynamodb;

public class DynamoDBErrors
{
public static final String ValidationException = "ValidationException";
public static final CharSequence ValidationExceptionMessageKey = "The AttributeValue for a key attribute cannot contain an empty string value.";
}
Loading

0 comments on commit d42bbca

Please sign in to comment.