11package com .sap .kafka .connect .source .hana
22
3+ import com .sap .kafka .client .hana .{HANAConfigInvalidInputException , HANAConfigMissingException , HANAJdbcClient }
4+ import com .sap .kafka .connect .config .BaseConfigConstants
5+ import com .sap .kafka .connect .config .hana .{HANAConfig , HANAParameters }
6+ import com .sap .kafka .utils .ExecuteWithExceptions
7+
38import java .util
4- import org .apache .kafka .common .config .ConfigDef
9+ import org .apache .kafka .common .config .{ ConfigDef , ConfigException }
510import org .apache .kafka .connect .connector .Task
6- import org .apache .kafka .connect .source .SourceConnector
11+ import org .apache .kafka .connect .errors .ConnectException
12+ import org .apache .kafka .connect .source .{SourceConnector , SourceConnectorContext }
713
814import scala .collection .JavaConverters ._
915
1016class HANASourceConnector extends SourceConnector {
11- private var configProperties : Option [util.Map [String , String ]] = None
12-
17+ private var configRawProperties : Option [util.Map [String , String ]] = None
18+ private var hanaClient : HANAJdbcClient = _
19+ private var tableOrQueryInfos : List [Tuple3 [String , Int , String ]] = _
20+ private var configProperties : HANAConfig = _
21+ override def context (): SourceConnectorContext = super .context()
1322 override def version (): String = getClass.getPackage.getImplementationVersion
1423
1524 override def start (properties : util.Map [String , String ]): Unit = {
16- configProperties = Some (properties)
25+ configRawProperties = Some (properties)
26+ configProperties = HANAParameters .getConfig(properties)
27+ hanaClient = new HANAJdbcClient (configProperties)
28+
29+ val topics = configProperties.topics
30+ var tables : List [(String , String )] = Nil
31+ if (topics.forall(topic => configProperties.topicProperties(topic).keySet.contains(" table.name" ))) {
32+ tables = topics.map(topic =>
33+ (configProperties.topicProperties(topic)(" table.name" ), topic))
34+ }
35+ var query : List [(String , String )] = Nil
36+ if (topics.forall(topic => configProperties.topicProperties(topic).keySet.contains(" query" ))) {
37+ query = topics.map(topic =>
38+ (configProperties.topicProperties(topic)(" query" ), topic))
39+ }
40+
41+ if (tables.isEmpty && query.isEmpty) {
42+ throw new ConnectException (" Invalid configuration: each HANAConnector must have one table or query associated" )
43+ }
44+
45+ tableOrQueryInfos = configProperties.queryMode match {
46+ case BaseConfigConstants .QUERY_MODE_TABLE =>
47+ getTables(hanaClient, tables)
48+ case BaseConfigConstants .QUERY_MODE_SQL =>
49+ getQueries(query)
50+ }
1751 }
1852
1953 override def taskClass (): Class [_ <: Task ] = classOf [HANASourceTask ]
2054
2155 override def taskConfigs (maxTasks : Int ): util.List [util.Map [String , String ]] = {
22- (1 to maxTasks).map(c => configProperties.get).toList.asJava
56+ val tableOrQueryGroups = createTableOrQueryGroups(tableOrQueryInfos, maxTasks)
57+ createTaskConfigs(tableOrQueryGroups, configRawProperties.get).asJava
2358 }
2459
2560 override def stop (): Unit = {
@@ -29,4 +64,92 @@ class HANASourceConnector extends SourceConnector {
2964 override def config (): ConfigDef = {
3065 new ConfigDef
3166 }
67+
68+ private def getTables (hanaClient : HANAJdbcClient , tables : List [Tuple2 [String , String ]]) : List [Tuple3 [String , Int , String ]] = {
69+ val connection = hanaClient.getConnection
70+
71+ // contains fullTableName, partitionNum, topicName
72+ var tableInfos : List [Tuple3 [String , Int , String ]] = List ()
73+ val noOfTables = tables.size
74+ var tablecount = 1
75+
76+ var stmtToFetchPartitions = s " SELECT SCHEMA_NAME, TABLE_NAME, PARTITION FROM SYS.M_CS_PARTITIONS WHERE "
77+ tables.foreach(table => {
78+ if (! (configProperties.topicProperties(table._2)(" table.type" ) == BaseConfigConstants .COLLECTION_TABLE_TYPE )) {
79+ table._1 match {
80+ case BaseConfigConstants .TABLE_NAME_FORMAT (schema, tablename) =>
81+ stmtToFetchPartitions += s " (SCHEMA_NAME = ' $schema' AND TABLE_NAME = ' $tablename') "
82+
83+ if (tablecount < noOfTables) {
84+ stmtToFetchPartitions += " OR "
85+ }
86+ tablecount = tablecount + 1
87+ case _ =>
88+ throw new HANAConfigInvalidInputException (" The table name is invalid. Does not follow naming conventions" )
89+ }
90+ }
91+ })
92+
93+ if (tablecount > 1 ) {
94+ val stmt = connection.createStatement()
95+ val partitionRs = stmt.executeQuery(stmtToFetchPartitions)
96+
97+ while (partitionRs.next()) {
98+ val tableName = " \" " + partitionRs.getString(1 ) + " \" .\" " + partitionRs.getString(2 ) + " \" "
99+ tableInfos :+= Tuple3 (tableName, partitionRs.getInt(3 ),
100+ tables.filter(table => table._1 == tableName).map(table => table._2).head.toString)
101+ }
102+ }
103+
104+ // fill tableInfo for tables whose entry is not in M_CS_PARTITIONS
105+ val tablesInInfo = tableInfos.map(tableInfo => tableInfo._1)
106+ val tablesToBeAdded = tables.filterNot(table => tablesInInfo.contains(table._1))
107+
108+ tablesToBeAdded.foreach(tableToBeAdded => {
109+ if (configProperties.topicProperties(tableToBeAdded._2)(" table.type" ) == BaseConfigConstants .COLLECTION_TABLE_TYPE ) {
110+ tableInfos :+= Tuple3 (getTableName(tableToBeAdded._1)._2, 0 , tableToBeAdded._2)
111+ } else {
112+ tableInfos :+= Tuple3 (tableToBeAdded._1, 0 , tableToBeAdded._2)
113+ }
114+ })
115+
116+ tableInfos
117+ }
118+
119+ private def getQueries (queryTuple : List [(String , String )]): List [(String , Int , String )] =
120+ queryTuple.map(query => (query._1, 0 , query._2))
121+
122+ private def createTableOrQueryGroups (tableOrQueryInfos : List [Tuple3 [String , Int , String ]], count : Int )
123+ : List [List [Tuple3 [String , Int , String ]]] = {
124+ val groupSize = count match {
125+ case c if c > tableOrQueryInfos.size => 1
126+ case _ => ((tableOrQueryInfos.size + count - 1 ) / count)
127+ }
128+ tableOrQueryInfos.grouped(groupSize).toList
129+ }
130+
131+ private def createTaskConfigs (tableOrQueryGroups : List [List [Tuple3 [String , Int , String ]]], config : java.util.Map [String , String ])
132+ : List [java.util.Map [String , String ]] = {
133+ tableOrQueryGroups.map(g => {
134+ var gconfig = new java.util.HashMap [String ,String ](config)
135+ for ((t, i) <- g.zipWithIndex) {
136+ gconfig.put(s " _tqinfos. $i.name " , t._1)
137+ gconfig.put(s " _tqinfos. $i.partition " , t._2.toString)
138+ gconfig.put(s " _tqinfos. $i.topic " , t._3)
139+ }
140+ gconfig
141+ })
142+ }
143+
144+ private def getTableName (tableName : String ): (Option [String ], String ) = {
145+ tableName match {
146+ case BaseConfigConstants .TABLE_NAME_FORMAT (schema, table) =>
147+ (Some (schema), table)
148+ case BaseConfigConstants .COLLECTION_NAME_FORMAT (table) =>
149+ (None , table)
150+ case _ =>
151+ throw new HANAConfigInvalidInputException (s " The table name mentioned in `{topic}.table.name` is invalid. " +
152+ s " Does not follow naming conventions " )
153+ }
154+ }
32155}
0 commit comments