Repository navigation
#875 Add the ability to validate options to spark-cobol without reading data files
#876
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5e4c6f4
dba7421
90c7513
6ec5f9e
853cf62
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| /* | ||
| * Copyright 2018 ABSA Group Limited | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package za.co.absa.cobrix.spark.cobol.builder | ||
|
|
||
| import org.apache.spark.sql.{DataFrame, SparkSession} | ||
| import za.co.absa.cobrix.cobol.reader.parameters.CobolParametersParser.PARAM_SOURCE_PATHS | ||
| import za.co.absa.cobrix.cobol.reader.parameters.{CobolParameters, CobolParametersParser, Parameters} | ||
| import za.co.absa.cobrix.cobol.reader.schema.CobolSchema | ||
| import za.co.absa.cobrix.spark.cobol.source.DefaultSource.buildEitherReader | ||
| import za.co.absa.cobrix.spark.cobol.source.copybook.CopybookContentLoader | ||
| import za.co.absa.cobrix.spark.cobol.source.parameters.{CobolParametersValidator, LocalityParameters} | ||
| import za.co.absa.cobrix.spark.cobol.source.{CobolRelation, DefaultSource} | ||
| import za.co.absa.cobrix.spark.cobol.writer.RecordCombinerSelector | ||
|
|
||
| import scala.collection.mutable | ||
|
|
||
| class ValidatorBuilder(implicit spark: SparkSession) { | ||
| private val caseInsensitiveOptions = new mutable.HashMap[String, String]() | ||
|
|
||
| def option(key: String, value: String): ValidatorBuilder = { | ||
| caseInsensitiveOptions += (key.toLowerCase -> value) | ||
| this | ||
| } | ||
|
|
||
| def options(options: Map[String, String]): ValidatorBuilder = { | ||
| caseInsensitiveOptions ++= options.map(kv => (kv._1.toLowerCase(), kv._2)) | ||
| this | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * This methods validates options when reading data files. | ||
| * | ||
| * Validates the list of data files against spark-cobol options. | ||
| * File paths can contain wildcards, e.g. "/somedur/test*" | ||
| */ | ||
| def validateLoad(filePaths: String*): Unit = { | ||
| val sqlContext = spark.sqlContext | ||
|
|
||
| // Validating options compatibility | ||
| val cobolOptions = caseInsensitiveOptions.toMap + (PARAM_SOURCE_PATHS -> filePaths.mkString(",")) | ||
| CobolParametersValidator.validateOrThrow(cobolOptions, spark.sparkContext.hadoopConfiguration) | ||
|
|
||
| val cobolParameters0: CobolParameters = CobolParametersParser.parse(new Parameters(caseInsensitiveOptions.toMap)) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| val varLenOptions = cobolParameters0.variableLengthParams.map(_.copy(generateRecordId = false)) | ||
| val cobolParameters: CobolParameters = cobolParameters0 | ||
| .copy(sourcePaths = filePaths, generateRecordBytes = false, debugIgnoreFileSize = false, variableLengthParams = varLenOptions) | ||
|
|
||
| // Validating cobol parameters after parsing | ||
| CobolParametersValidator.checkSanity(cobolParameters) | ||
|
|
||
| val isRecursiveRetrieval = DefaultSource.isRecursiveRetrieval(sqlContext) | ||
| val filesList = CobolRelation.getListFilesWithOrder(filePaths, sqlContext, isRecursiveRetrieval) | ||
| val hasGpg = cobolParameters.gpgPrivateKey.isDefined | ||
| val hasCompressedFiles = hasGpg || filesList.exists(_.isCompressed) | ||
|
|
||
| val relation = new CobolRelation(cobolParameters.sourcePaths, | ||
| filesList, | ||
| buildEitherReader(sqlContext.sparkSession, cobolParameters, hasCompressedFiles), | ||
| LocalityParameters.extract(cobolParameters), | ||
| cobolParameters.debugIgnoreFileSize, | ||
| cobolParameters.recordLimit)(sqlContext) | ||
|
|
||
| // Validating reader requirements | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift Check every fixed-length file before validation succeeds. If a directory contains at least 50 files, the scanner examines only its first 50 for record-size mismatches. 🤖 Prompt for AI Agents |
||
| relation.validateRelation() | ||
| } | ||
|
|
||
| /** | ||
| * This method validates options when writing a DataFrame. | ||
| * | ||
| * Validates spark-cobol options are consistent with a write request. | ||
| */ | ||
| def validateSave(df: DataFrame): Unit = { | ||
| val cobolOptions = caseInsensitiveOptions.toMap | ||
|
|
||
| val cobolParameters = CobolParametersParser.parse(new Parameters(cobolOptions), isWriter = true) | ||
| CobolParametersValidator.checkSanity(cobolParameters) | ||
|
|
||
| val readerParameters = CobolParametersParser.getReaderProperties(cobolParameters, None) | ||
| CobolParametersValidator.validateParametersForWriting(readerParameters) | ||
|
|
||
| val copybookContent = CopybookContentLoader.load(cobolParameters, spark.sparkContext.hadoopConfiguration) | ||
| val cobolSchema = CobolSchema.fromReaderParameters(copybookContent, readerParameters) | ||
| val combiner = RecordCombinerSelector.selectCombiner(cobolSchema, readerParameters) | ||
| combiner.combine(df, cobolSchema, readerParameters) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,9 +26,9 @@ import scala.collection.JavaConverters._ | |
| import scala.util.control.NonFatal | ||
|
|
||
| /** | ||
| * This object provides utility methods for interacting with HDFS internals. | ||
| * This object provides utility methods for interacting with Hadoop filesystem internals. | ||
| */ | ||
| object HDFSUtils { | ||
| object HadoopFsUtils { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Preserve the public External Scala code that imports 🤖 Prompt for AI Agents |
||
| private val log: Logger = LoggerFactory.getLogger(this.getClass) | ||
| final val bytesInMegabyte = 1048576 | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: AbsaOSS/cobrix
Length of output: 1013
🏁 Script executed:
Repository: AbsaOSS/cobrix
Length of output: 10442
Define the input path before validating the read options.
inpudDataPathis not declared in the README example. Renaming only the call toinputDataPathwould leave the example with another undefined identifier.🐛 Suggested fix
🤖 Prompt for AI Agents