Compare commits

...

9 commits

Author SHA1 Message Date
eelke
ee1a8ba48d Add save and revert button to datagrid no implicit saves any more. 2026-10-18 11:34:00 +02:00
eelke
66267334c8 We should not allow editing of system catalogs. 2026-10-18 10:35:39 +02:00
eelke
5e7fb68c55 QueryTool now uses the same DataGrid as the CRUD tool. 2026-10-18 10:29:24 +02:00
eelke
bbe52c9db8 Generalise CrudModel to mapping-based editing 2026-10-18 10:17:38 +02:00
eelke
35eac713ff Implement ResultSourceMapping 2026-10-18 07:48:33 +02:00
eelke
21e8616ead Add reno skill 2026-10-18 07:47:46 +02:00
eelke
7577a39dd9 Add IDE files 2026-10-18 07:45:03 +02:00
eelke
f0252a1f49 Remove old qmake project files. 2026-10-18 07:05:03 +02:00
eelke
49f6619a4e Switch to cmake + vcpkg, builds on linux, windows needs verification.
Code has been modified to be portable.
2026-10-17 16:21:22 +02:00
74 changed files with 2069 additions and 1427 deletions

10
.idea/.gitignore generated vendored Normal file
View file

@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

6
.idea/QtSettings.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="QtSettings">
<option name="myCurrentProfile" value="Debug" />
</component>
</project>

367
.idea/editor.xml generated Normal file
View file

@ -0,0 +1,367 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="BackendCodeEditorSettings">
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CDeclarationWithImplicitIntType/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CommentTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConstevalIfIsAlwaysConstant/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractClassWithoutSpecifier/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractFinalClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractVirtualFunctionCallInCtor/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAccessSpecifierWithNoDeclarations/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAwaiterTypeIsNotClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBooleanIncrementExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatBadCode/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatLegacyCode/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatMixedArgs/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooFewArgs/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooManyArgs/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCStyleCast/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCVQualifierCanNotBeAppliedToReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassCanBeFinal/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassIsIncomplete/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeedsConstructorBecauseOfUninitializedMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCompileTimeConstantCanBeReplacedWithBooleanConstant/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConceptNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConditionalExpressionCanBeSimplified/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstParameterInDeclaration/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstValueFunctionReturnType/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstexprIfDiscardedBranch/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCoroutineCallResolveError/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaConflictingMemorySpaceSpecifiers/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaForLoopCanBeReplacedWithIRange/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaFunctionIsNotCallableFromExecutionSpace/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaGotoStatementIsNotAllowedInTileGlobalFunction/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaGridConstantIsOnlyAllowedOnKernelParameter/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaGridConstantParameterMustBeConstNonReference/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaIRangeStepMustBePositive/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaMemorySpaceLocalVariableMustBeExternOrStatic/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaMemorySpaceSpecifierIsNotAllowedOnDataMember/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaMemorySpaceSpecifierIsNotAllowedOnParameter/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaMemorySpaceSpecifierIsNotAllowedOnTypeAlias/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaMemorySpaceVariableIsNotAllowedInHostFunction/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaReturnStatementInsideLoopIsNotAllowedInTileGlobalFunction/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaSharedVariableCannotHaveInitializer/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaTileKernelLaunchMustHaveBlockSizeOne/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaVariableIsNotAccessibleFromExecutionSpace/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAArrayIndexOutOfBounds/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantConditions/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantFunctionResult/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantParameter/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFADeletedPointer/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAEndlessLoop/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInfiniteRecursion/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInvalidatedMemory/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALocalValueEscapesFunction/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALocalValueEscapesScope/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALoopConditionNotUpdated/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAMemoryLeak/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFANotInitializedField/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFANullDereference/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFATimeOver/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableCode/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableFunctionCall/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreadVariable/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnusedValue/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationHidesLocal/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationHidesUncapturedLocal/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationSpecifierWithoutDeclarators/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorDisambiguatedAsFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorUsedBeforeInitialization/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultCaseNotHandledInSwitchStatement/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultInitializationWithNoUserConstructor/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultIsUsedAsIdentifier/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultedSpecialMemberFunctionIsImplicitlyDeleted/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefinitionsOrder/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeletingVoidPointer/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDependentTemplateWithoutTemplateKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDependentTypeWithoutTypenameKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedEntity/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedOverridenMethod/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedRegisterStorageClassSpecifier/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDereferenceOperatorLimitExceeded/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDiscardedPostfixOperatorResult/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenSyntaxError/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenUndocumentedParameter/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenUnresolvedReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEmptyDeclaration/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceCVQualifiersOrder/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceCVQualifiersPlacement/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceDoStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceForStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceFunctionDeclarationStyle/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceIfStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceNestedNamespacesStyle/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceOverridingDestructorStyle/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceOverridingFunctionStyle/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceTypeAliasCodeStyle/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceWhileStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEntityAssignedButNoRead/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEntityUsedOnlyInUnevaluatedContext/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnumeratorNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEqualOperandsInBinaryExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEvaluationFailure/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppExplicitSpecializationInNonNamespaceScope/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppExpressionWithoutSideEffects/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFinalFunctionInFinalClass/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFinalNonOverridingVirtualFunction/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppForLoopCanBeReplacedWithWhile/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppForwardEnumDeclarationWithoutUnderlyingType/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionDoesntReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionIsNotImplemented/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionResultShouldBeUsed/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionalStyleCast/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHeaderHasBeenAlreadyIncluded/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHiddenFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHidingFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIdenticalOperandsInBinaryExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIfCanBeReplacedByConstexprIf/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppImplicitDefaultConstructorNotAvailable/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompatiblePointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompleteSwitchStatement/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInconsistentNaming/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIntegralToPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInvalidLineContinuation/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppJoinDeclarationAndAssignment/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLambdaCaptureNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMayBeConst/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableWithNonTrivialDtorIsNeverUsed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLongFloat/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeConst/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeStatic/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberInitializersOrder/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMismatchedClassTags/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMissingIncludeGuard/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMissingKeywordThrow/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppModulePartitionWithSeveralPartitionUnits/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtAddressOfClassRValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtBindingRValueToLvalueReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtCopyElisionInCopyInitDeclarator/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtDoubleUserConversionInCopyInit/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtNotInitializedStaticConstLocalVar/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtReinterpretCastFromNullptr/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMultiCharacterLiteral/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMultiCharacterWideLiteral/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMustBePublicVirtualToImplementInterface/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMutableSpecifierOnReferenceMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNoDiscardExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNodiscardFunctionWithoutReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExceptionSafeResourceAcquisition/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExplicitConversionOperator/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExplicitConvertingConstructor/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonInlineFunctionDefinitionInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonInlineVariableDefinitionInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNotAllPathsReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppObjectMemberMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppOutParameterMustBeWritten/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppOverrideWithDifferentVisibility/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConst/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConstPtrOrRef/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterNamesMismatch/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterNeverUsed/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPassValueParameterByConstReference/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPointerConversionDropsQualifiers/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPointerToIntegralConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPolymorphicClassWithNonVirtualPublicDestructor/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyErroneousEmptyStatements/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyUninitializedMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyUnintendedObjectSlicing/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrecompiledHeaderIsNotIncluded/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrecompiledHeaderNotFound/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfBadFormat/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfExtraArg/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfMissedArg/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfRiskyFormat/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrivateSpecialMemberFunctionIsNotImplemented/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRangeBasedForIncompatibleReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedefinitionOfDefaultArgumentInOverrideFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantAccessSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBaseClassAccessSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBaseClassInitializer/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBooleanExpressionArgument/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantCastExpression/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantComplexityInComparison/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantConditionalExpression/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantConstSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantControlFlowJump/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantDereferencingAndTakingAddress/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElaboratedTypeSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElseKeyword/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElseKeywordInsideCompoundStatement/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantEmptyDeclaration/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantEmptyStatement/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantExportKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantFwdClassOrEnumSpecifier/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantInlineSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantLambdaParameterList/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantMemberInitializer/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantNamespaceDefinition/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantParentheses/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantQualifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantQualifierADL/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantStaticSpecifierOnMemberAllocationFunction/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantStaticSpecifierOnThreadLocalLocalVariable/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTemplateArguments/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTemplateKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTypenameKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantVoidArgumentList/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantZeroInitializerInAggregateInitialization/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReferenceToOverriddenVirtualFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReinterpretCastFromVoidPtr/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRemoveRedundantBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReplaceMemsetWithZeroInitialization/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReplaceTieWithStructuredBinding/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReturnNoValueInNonVoidFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSmartPointerVsMakeFunction/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSomeObjectMembersMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSpecialFunctionWithoutNoexceptSpecification/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticAssertFailure/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticDataMemberInUnnamedStruct/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticSpecifierOnAnonymousNamespaceMember/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStringLiteralToCharPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSyntaxWarning/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTabsAreDisallowed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateArgumentsCanBeDeduced/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterNeverUsed/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterShadowing/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppThrowExpressionCanBeReplacedWithRethrow/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTooWideScope/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTooWideScopeInitStatement/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTypeAliasNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUninitializedDependentBaseClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUninitializedNonStaticDataMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnionMemberOfReferenceType/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaEndRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnamedNamespaceInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnecessaryWhitespace/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnsignedZeroComparison/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnusedIncludeDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAlgorithmWithCount/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAssociativeContains/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAuto/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAutoForNumeric/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseDesignatedInitializers/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseElementsView/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseEraseAlgorithm/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseFamiliarTemplateSyntaxForGenericLambdas/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseInternalLinkage/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseRangeAlgorithm/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseStdSize/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseStructuredBinding/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseTypeTraitAlias/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUserDefinedLiteralSuffixDoesNotStartWithUnderscore/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUsingResultOfAssignmentAsCondition/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVariableCanBeMadeConstexpr/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVirtualFunctionCallInsideCtor/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVirtualFunctionInFinalClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVolatileParameterInDeclaration/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWarningDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWrongIncludesOrder/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWrongSlashesInIncludeDirective/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppZeroConstantCanBeReplacedWithNullptr/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppZeroValuedExpressionUsedAsNullPointer/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=IdentifierTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=IfStdIsConstantEvaluatedCanBeReplaced/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StdIsConstantEvaluatedWillAlwaysEvaluateToConstant/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StringLiteralTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppClangFormat/EnableClangFormatSupport/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_ARGUMENT/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_BINARY_EXPRESSIONS_CHAIN/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_CALLS_CHAIN/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_EXPRESSION/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_EXTENDS_LIST/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_FOR_STMT/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_PARAMETER/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_TYPE_ARGUMENT/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_TYPE_PARAMETER/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTIPLE_DECLARATION/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_TERNARY/@EntryValue" value="ALIGN_ALL" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_CLASS_DEFINITION/@EntryValue" value="1" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_DECLARATIONS/@EntryValue" value="0" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_FUNCTION_DECLARATION/@EntryValue" value="1" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_FUNCTION_DEFINITION/@EntryValue" value="1" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BREAK_TEMPLATE_DECLARATION/@EntryValue" value="LINE_BREAK" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/CASE_BLOCK_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/CONTINUOUS_LINE_INDENT/@EntryValue" value="Double" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_ACCESS_SPECIFIERS_FROM_CLASS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CASE_FROM_SWITCH/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CLASS_MEMBERS_FROM_ACCESS_SPECIFIERS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_COMMENT/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_SIZE/@EntryValue" value="4" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_STYLE/@EntryValue" value="Space" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INITIALIZER_BRACES/@EntryValue" value="END_OF_LINE_NO_SPACE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INT_ALIGN_EQ/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INVOCABLE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_CODE/@EntryValue" value="2" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue" value="2" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_USER_LINEBREAKS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/LINE_BREAK_AFTER_COLON_IN_MEMBER_INITIALIZER_LISTS/@EntryValue" value="ON_SINGLE_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/MEMBER_INITIALIZER_LIST_STYLE/@EntryValue" value="DO_NOT_CHANGE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_INDENTATION/@EntryValue" value="All" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/OTHER_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_CATCH_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_ELSE_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_NAMESPACE_DEFINITIONS_ON_SAME_LINE/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_WHILE_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SIMPLE_BLOCK_STYLE/@EntryValue" value="DO_NOT_CHANGE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_CAST_EXPRESSION_PARENTHESES/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COLON_IN_BITFIELD_DECLARATOR/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COMMA_IN_TEMPLATE_ARGS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COMMA_IN_TEMPLATE_PARAMS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_EXTENDS_COLON/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_FOR_COLON/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_FOR_SEMICOLON/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_DATA_MEMBER/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_DATA_MEMBERS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_METHOD/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_NESTED_DECLARATOR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_DATA_MEMBER/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_DATA_MEMBERS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_METHOD/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_UNARY_OPERATOR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_COLON_IN_BITFIELD_DECLARATOR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_EXTENDS_COLON/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_FOR_COLON/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_FOR_SEMICOLON/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_ABSTRACT_DECL/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_DATA_MEMBER/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_DATA_MEMBERS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_METHOD/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_ABSTRACT_DECL/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_DATA_MEMBER/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_DATA_MEMBERS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_METHOD/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_TEMPLATE_ARGS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_TEMPLATE_PARAMS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BETWEEN_CLOSING_ANGLE_BRACKETS_IN_TEMPLATE_ARGS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_ARRAY_ACCESS_BRACKETS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_CAST_EXPRESSION_PARENTHESES/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_DECLARATION_PARENTHESES/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_BLOCKS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_INITIALIZER_BRACES/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_METHOD_PARENTHESES/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_TEMPLATE_PARAMS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_INITIALIZER_BRACES/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_TEMPLATE_ARGS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_TEMPLATE_PARAMS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPECIAL_ELSE_IF_TREATMENT/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/TAB_WIDTH/@EntryValue" value="4" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/TYPE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_BINARY_OPSIGN/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_DECLARATION_LPAR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_INVOCATION_LPAR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_ARGUMENTS_STYLE/@EntryValue" value="WRAP_IF_LONG" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_DECLARATION_LPAR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_DECLARATION_RPAR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_INVOCATION_LPAR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_INVOCATION_RPAR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_TERNARY_OPSIGNS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_PARAMETERS_STYLE/@EntryValue" value="WRAP_IF_LONG" type="string" />
<option name="/Default/CodeStyle/EditorConfig/EnableClangFormatSupport/@EntryValue" value="false" type="bool" />
</component>
</project>

4
.idea/encodings.xml generated Normal file
View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

7
.idea/misc.xml generated Normal file
View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakePythonSetting">
<option name="pythonIntegrationState" value="YES" />
</component>
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
</project>

8
.idea/modules.xml generated Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/pgLab.iml" filepath="$PROJECT_DIR$/.idea/pgLab.iml" />
</modules>
</component>
</project>

2
.idea/pgLab.iml generated Normal file
View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CIDR" type="CPP_MODULE" version="4" />

6
.idea/vcs.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View file

@ -0,0 +1,110 @@
---
name: reno-release-notes
description: Create, review and lint pgLab release notes with reno (releasenotes/notes YAML fragments). Use whenever the user mentions reno, release notes, releasenotes/notes, "reno new", "reno lint", or asks to add or check a note for a user-visible change in pgLab, even if reno is not named explicitly.
---
# reno release notes in pgLab
pgLab uses [reno](https://docs.openstack.org/reno/latest/) for release notes.
Each user-visible change adds its own small YAML file in `releasenotes/notes/`,
committed together with the change. reno derives the release from git tags, so
never move or rename notes to change releases. Content is reStructuredText, not
Markdown.
The notes are rendered in the Sphinx user docs through the
`.. release-notes::` directive in `docs/releasenotes.rst` (docs build:
`pip install -r docs/requirements.txt && sphinx-build -b html docs userdocs`).
The About dialog links to the generated `releasenotes.html`.
## When to write a note
Write one for: new features, behaviour changes users will notice (UI, menus,
shortcuts, query/code generation output, connection handling), bug fixes,
known issues, and upgrade impact (e.g. changed stored settings or connection
config format).
Skip: pure refactors, build-system/CI changes, test-only changes, internal
code cleanup, docs-only edits.
## Sections configured for this project
Defined in `releasenotes/config.yaml` (render order). Only these keys are valid;
others (`deprecations`, `critical`) are NOT configured here and will not render.
| Key | Rendered title | Use for |
|------------|------------------|--------------------------------------------|
| `prelude` | (implicit) | Release-wide summary, single string |
| `features` | New Features | New functionality |
| `other` | Changes | Changes to existing behaviour, minor items |
| `issues` | Known Issues | Known problems / limitations |
| `upgrade` | Upgrade Notes | What users must do or know when upgrading |
| `security` | Security Issues | Security fixes |
| `fixes` | Bug Fixes | Ordinary bug fixes |
Every section except `prelude` is a list of strings. Encoding is utf8.
## Creating a note
From the repo root (`pip install reno` if missing):
```bash
reno new short-descriptive-slug
```
This creates `releasenotes/notes/short-descriptive-slug-<16 hex>.yaml`. Keep the
random suffix. Existing examples: `f5-bindings-…`,
`connection-details-in-window-title-…`, `dollar-quoted-string-…`.
Delete all unused template sections, keep only what you need.
```yaml
---
features:
- |
The query tool now executes the current statement with
:kbd:`F5`.
fixes:
- |
Fixed reconnect dialog that kept popping up after the connection was lost.
```
Short single-line items may be plain (`- text`), longer ones use `- |`.
Wrap at roughly 80 columns.
## Writing style
- Write for pgLab users, not patch reviewers: describe the visible effect, not
the implementation (no class or function names unless users see them).
- Past or present tense, full sentences, one bullet per distinct change.
- RST markup: inline literals with double backticks (``` ``pg_catalog`` ```),
links as `` `text <https://example.com>`_ ``, GUI menu paths with
`:menuselection:` (e.g. `:menuselection:`Help --> About``), keys with
`:kbd:`. No Markdown, no single-backtick code.
- Mention the PostgreSQL feature or object type involved when relevant.
- For `upgrade`, state the action required.
## Validate before committing
```bash
reno lint
reno list # note must be tracked by git to appear for tagged history
```
Common failures: YAML indentation/tabs, misspelled section key, a string where
a list is required (or a list for `prelude`), an unconfigured section, bad RST.
Optionally build the docs (command above) to check rendering.
## Commit rules
- `git add releasenotes/notes/<file>` and commit it in the same commit as the
change, not a follow-up.
- Commit messages are plain imperative sentences (e.g. "Fix reconnect dialog
popping up repeatedly.").
- To correct a note, edit the same file; don't add a second one.
## Troubleshooting
- Note missing in report: not committed, or shallow clone without tags
(`git fetch --tags --unshallow`).
- Wrong release: decided by git history/tags; check `reno list`.
- Broken rendering: almost always RST (single backticks, Markdown links,
indentation).

32
AGENTS.md Normal file
View file

@ -0,0 +1,32 @@
# AGENTS.md
pgLab: Qt 6 / C++20 desktop GUI for PostgreSQL.
## Layout (link order: core, pgsql -> pglablib -> pglab)
- `core/` – generic utilities (SQL lexer, CSV writer, `Expected`, JSON); no DB access.
- `pgsql/` – thin C++ wrapper around libpq (connection, result, params, oids).
- `pglablib/` – catalog models, code builders, query generation, connection config.
- `pglab/` – the Qt Widgets application (executable).
- `tests/` – gtest executables `PgsqlTests` and `pglabtests`, plus `mygtestutils` (Qt `PrintTo` helpers).
- `docs/` – Sphinx user docs; `releasenotes/notes` – `reno` notes.
## Build and test
CMake + vcpkg is the active build system (`VCPKG_ROOT` must be set). Qt 6 is not provided by vcpkg; it must be installed on the system.
```
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake
cmake --build build
ctest --test-dir build --output-on-failure
```
- Run one suite: `./build/tests/pglabtests/pglabtests --gtest_filter='SqlLexer*'` (same for `PgsqlTests`).
- `-DBUILD_TESTS=OFF` skips tests. `build/` is git-ignored; the first configure builds dependencies and is slow.
- Run the app headless with `QT_QPA_PLATFORM=offscreen ./build/pglab/pglab`. It needs a PostgreSQL server for anything useful.
## Gotchas
- Tests are registered per executable, so each `ctest` entry covers a whole suite.
- A new test file must be added to the `add_executable` list in `tests/<suite>/CMakeLists.txt`. Sources are listed explicitly, with no globbing. The same goes for new sources in each library's `CMakeLists.txt`.
- `core/test/` is not referenced by any CMake file (its tests are built from `tests/pglabtests`). Don't expect changes there to be compiled.
- `AUTOMOC`/`AUTOUIC`/`AUTORCC` are on globally, so `.ui` and `.qrc` files only need to be listed as sources.
- `BOOST_ENABLE_ASSERT_HANDLER` is defined; the handler lives in `core/my_boost_assert_handler.cpp`.
- Commit messages are plain imperative sentences. User-visible changes need a reno note (`releasenotes/notes`).
- Docs build: `pip install -r docs/requirements.txt && sphinx-build -b html docs userdocs`.
- Windows (VS 2022) is also a supported platform. Avoid Linux-only code, and don't break `WIN32_LEAN_AND_MEAN`/`NOMINMAX` assumptions.

30
CMakeLists.txt Normal file
View file

@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 3.21)
project(pgLab LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
add_compile_definitions(BOOST_ENABLE_ASSERT_HANDLER QT_DEPRECATED_WARNINGS)
if(WIN32)
add_compile_definitions(WIN32_LEAN_AND_MEAN NOMINMAX)
endif()
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Sql Concurrent)
option(BUILD_TESTS "Build the gtest executables and register them with ctest" ON)
add_subdirectory(core)
add_subdirectory(pgsql)
add_subdirectory(pglablib)
add_subdirectory(pglab)
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()

View file

@ -11,3 +11,41 @@ The release notes use collected from git using [reno](https://docs.openstack.org
- googletest - googletest
- libpq - libpq
Boost, Botan, libpq and googletest are provided by vcpkg through the manifest in `vcpkg.json`, so you do not install them manually.
## Required tools
- CMake 3.21 or newer
- A C++20 compiler (GCC or Clang on Linux, Visual Studio 2022 on Windows)
- Qt 6 with the Core, Gui, Widgets, Sql and Concurrent components
- vcpkg, with the `VCPKG_ROOT` environment variable pointing to its checkout
- Ninja or make (optional, for faster builds)
- A PostgreSQL server (only needed to run the application against a database)
## Building
Configure with the vcpkg toolchain file. The first configure downloads and builds the dependencies, which takes a while:
```
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake
cmake --build build
```
To skip the test executables, add `-DBUILD_TESTS=OFF` to the configure command.
## Running the tests
From the build directory, run:
```
ctest --output-on-failure
```
## Running the application
The executable is built to `build/pglab/pglab`. On a machine without a display, set `QT_QPA_PLATFORM=offscreen`:
```
./build/pglab/pglab
```

View file

@ -1,15 +0,0 @@
! include( ./local.pri ) {
error( "Use local.pri.sample to create your own local.pri" )
}
LIBS += -lUser32 -lws2_32 -llibpq
CONFIG += c++20
# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
DEFINES += BOOST_ENABLE_ASSERT_HANDLER
DEFINES += WIN32_LEAN_AND_MEAN NOMINMAX _WIN32_WINNT=0x0501

View file

@ -1,41 +1,31 @@
set(CMAKE_AUTOMOC ON)
add_library(core STATIC add_library(core STATIC
BackupFormatModel.cpp
CsvWriter.cpp
my_boost_assert_handler.cpp my_boost_assert_handler.cpp
PasswordManager.cpp SqlLexer.cpp
SqlLexer.cpp) CsvWriter.cpp
BackupFormatModel.cpp
target_link_libraries(core PUBLIC ExplainTreeModelItem.cpp
boost jsoncpp.cpp
botan SqlLexer.h
Qt5::Core ScopeGuard.h
CsvWriter.h
BackupFormatModel.h
Expected.h
ExplainTreeModelItem.h
json/json.h
std_utils.h
IntegerRange.h
) )
target_include_directories(core INTERFACE target_link_libraries(core PUBLIC
Qt6::Core
)
target_include_directories(core PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
) )
set_target_properties(core PROPERTIES set_target_properties(core PROPERTIES
CXX_STANDARD 14 CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON CXX_STANDARD_REQUIRED ON
POSITION_INDEPENDENT_CODE True POSITION_INDEPENDENT_CODE ON
) )
add_executable(runtests
test/main.cpp
test/tst_CsvWriter.cpp
test/tst_expected.cpp
test/tst_PasswordManager.cpp
test/tst_scopeguard.cpp
test/tst_SqlLexer.cpp
)
target_link_libraries(runtests
core
gtest
Threads::Threads
)
add_test(tests runtests)

View file

@ -1,43 +0,0 @@
#-------------------------------------------------
#
# Project created by QtCreator 2017-02-26T10:51:14
#
#-------------------------------------------------
QT -= gui
TARGET = core
TEMPLATE = lib
CONFIG += staticlib
! include( ../common.pri ) {
error( "Couldn't find the common.pri file!" )
}
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += my_boost_assert_handler.cpp \
SqlLexer.cpp \
CsvWriter.cpp \
BackupFormatModel.cpp \
ExplainTreeModelItem.cpp \
jsoncpp.cpp
HEADERS += \
SqlLexer.h \
ScopeGuard.h \
CsvWriter.h \
BackupFormatModel.h \
Expected.h \
ExplainTreeModelItem.h \
json/json.h \
std_utils.h \
IntegerRange.h
unix {
target.path = /usr/lib
INSTALLS += target
}

View file

@ -1,17 +0,0 @@
# Need paths here to find following library headers
# - botan
# - boost
# - libpq
# ???
INCLUDEPATH += c:/prog/include
INCLUDEPATH += c:/prog/include/pgsql
INCLUDEPATH += c:/VSproj/boost/include/boost-1_68
# Need paths here to find library files for
# - botan
# - boost
# - libpq
#
LIBS += -L\VSproj\boost\lib -L/PROG/LIB
GOOGLETEST_DIR = c:/prog/googletest

View file

@ -1,69 +1,115 @@
cmake_minimum_required(VERSION 3.2) set(PGLAB_SOURCES
project(pglab) main.cpp
About.cpp
catalog/delegates/IconColumnDelegate.cpp
catalog/models/BaseTableModel.cpp
catalog/models/ColumnTableModel.cpp
catalog/models/ConstraintModel.cpp
catalog/models/DatabasesTableModel.cpp
catalog/models/DependantsTableModel.cpp
catalog/models/IndexModel.cpp
catalog/models/ProcTableModel.cpp
catalog/models/RolesTableModel.cpp
catalog/models/SequenceModel.cpp
catalog/models/TriggerTableModel.cpp
catalog/tables/TablesTableModel.cpp
catalog/widgets/CatalogConstraintPage.cpp
catalog/widgets/CatalogFunctionsPage.cpp
catalog/widgets/CatalogIndexPage.cpp
catalog/widgets/CatalogInspector.cpp
catalog/widgets/CatalogNamespacePage.cpp
catalog/widgets/CatalogPageBase.cpp
catalog/widgets/CatalogSequencesPage.cpp
catalog/widgets/CatalogTablesPage.cpp
catalog/widgets/CatalogTypesPage.cpp
catalog/widgets/ColumnPage.cpp
catalog/widgets/DependantsPage.cpp
catalog/widgets/TriggerPage.cpp
codeeditor/CodeEditor.cpp
codeeditor/EditorGutter.cpp
codeeditor/GutterPainter.cpp
crud/CrudModel.cpp
crud/CrudTab.cpp
crud/DataGridWidget.cpp
querytool/QueryExplainModel.cpp
querytool/QueryParamListController.cpp
querytool/QueryTool.cpp
serverinspector/DatabasesPage.cpp
serverinspector/RolesPage.cpp
serverinspector/ServerInspector.cpp
util/Colors.cpp
util/PgLabItemDelegate.cpp
util/PgLabTableView.cpp
util/SqlSyntaxHighlighter.cpp
ConnectionConfigurationWidget.cpp
ConnectionController.cpp
ManagedPage.cpp
PgDumpOutputHighlighter.cpp
CreateDatabaseDialog.cpp
ConnectionManagerWindow.cpp
ConnectionListModel.cpp
SslModeModel.cpp
stopwatch.cpp
tuplesresultwidget.cpp
BackupDialog.cpp
MasterController.cpp
ParamTypeDelegate.cpp
OpenDatabase.cpp
ProcessStdioWidget.cpp
ResultTableModelUtil.cpp
NamespaceItemModel.cpp
PropertyProxyModel.cpp
CodeGenerator.cpp
UserConfiguration.cpp
widgets/SqlCodePreview.cpp
CustomFilterSortModel.cpp
PropertiesPage.cpp
PasswordPromptDialog.cpp
EditTableWidget.cpp
EditColumnTableModel.cpp
DatabaseWindow.cpp
widgets/SingleRecordModel.cpp
widgets/SingleRecordWidget.cpp
)
set(CMAKE_AUTOMOC ON) set(PGLAB_FORMS
set(CMAKE_AUTOUIC ON) ConnectionManagerWindow.ui
set(CMAKE_AUTORCC ON) CreateDatabaseDialog.ui
DatabaseWindow.ui
tuplesresultwidget.ui
querytool/QueryTab.ui
ProcessStdioWidget.ui
crud/CrudTab.ui
CodeGenerator.ui
widgets/SingleRecordWidget.ui
)
add_executable(pglab add_executable(pglab
ASyncDBConnection.cpp ${PGLAB_SOURCES}
ASyncWindow.cpp ${PGLAB_FORMS}
BackupDialog.cpp
BackupRestore.cpp
CodeBuilderConfiguration.cpp
ConnectionConfig.cpp
ConnectionList.cpp
ConnectionListModel.cpp
ConnectionManagerWindow.cpp
DatabaseInspectorWidget.cpp
DatabasesTableModel.cpp
DatabaseWindow.cpp
ExplainTreeModelItem.cpp
GlobalIoService.cpp
jsoncpp.cpp
main.cpp
MainWindow.cpp
MasterController.cpp
OpenDatabase.cpp
ParamListModel.cpp
ParamTypeDelegate.cpp
PgAuthIdContainer.cpp
PgAuthId.cpp
PgClass.cpp
PgDatabaseCatalogue.cpp
PgDatabaseContainer.cpp
PgDatabase.cpp
PgNamespace.cpp
PgTypeContainer.cpp
PgType.cpp
ProcessStdioWidget.cpp
QueryExplainModel.cpp
QueryResultModel.cpp
QueryTab.cpp
RolesTableModel.cpp
ServerWindow.cpp
SqlSyntaxHighlighter.cpp
stopwatch.cpp
tsqueue.cpp
tuplesresultwidget.cpp
TypeSelectionItemModel.cpp
util.cpp
resources.qrc resources.qrc
) )
set_target_properties(pglab PROPERTIES if(WIN32)
CXX_STANDARD 14 set_target_properties(pglab PROPERTIES WIN32_EXECUTABLE ON)
CXX_STANDARD_REQUIRED ON)
if(CMAKE_COMPILER_IS_GNUCXX)
# target_compile_options(pglab PRIVATE -Wall -fpic -march=native )
# set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og")
endif() endif()
target_link_libraries( pglab target_link_libraries(pglab PRIVATE
core pglablib
pgsql pgsql
boost-system core
Qt5::Widgets Qt6::Core
pthread Qt6::Gui
Qt6::Widgets
Qt6::Sql
Qt6::Concurrent
)
target_include_directories(pglab PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
set_target_properties(pglab PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
) )

View file

@ -3,7 +3,7 @@
#include <QDialog> #include <QDialog>
#include "catalog\PgDatabase.h" #include "catalog/PgDatabase.h"
class PgDatabaseCatalog; class PgDatabaseCatalog;

View file

@ -148,7 +148,7 @@
<customwidget> <customwidget>
<class>CodeEditor</class> <class>CodeEditor</class>
<extends>QPlainTextEdit</extends> <extends>QPlainTextEdit</extends>
<header>codeeditor\CodeEditor.h</header> <header>codeeditor/CodeEditor.h</header>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>

View file

@ -169,7 +169,7 @@ QVariant DatabasesTableModel::getData(const QModelIndex &index) const
break; break;
case SizeCol: case SizeCol:
if (d.size.totalBytes >= 0) if (d.size.totalBytes >= 0)
v = d.size.totalBytes; v = QVariant::fromValue(d.size.totalBytes);
break; break;
case AclCol: case AclCol:
v = db.aclString(); v = db.aclString();

View file

@ -114,7 +114,7 @@ QVariant IndexModel::getData(const QModelIndex &index) const
break; break;
case SizeCol: case SizeCol:
v = dat.sizeBytes; v = QVariant::fromValue(dat.sizeBytes);
break; break;
case ExplicitIndexCol: case ExplicitIndexCol:
v = !dat.isSupportingIndex(); v = !dat.isSupportingIndex();

View file

@ -102,12 +102,12 @@ QVariant SequenceModel::data(const QModelIndex &index, int role) const
case NameCol: return seq.objectName(); case NameCol: return seq.objectName();
case SchemaCol: return seq.nsName(); case SchemaCol: return seq.nsName();
case OwnerCol: return seq.ownerName(); case OwnerCol: return seq.ownerName();
case LastCol: return seq.last; case LastCol: return QVariant::fromValue(seq.last);
case StartCol: return seq.start; case StartCol: return QVariant::fromValue(seq.start);
case MinCol: return seq.min; case MinCol: return QVariant::fromValue(seq.min);
case MaxCol: return seq.max; case MaxCol: return QVariant::fromValue(seq.max);
case IncrementCol: return seq.increment; case IncrementCol: return QVariant::fromValue(seq.increment);
case CacheCol: return seq.cache; case CacheCol: return QVariant::fromValue(seq.cache);
case CycledCol: return seq.cycled; case CycledCol: return seq.cycled;
case CalledCol: return seq.called; case CalledCol: return seq.called;
case AclCol: return seq.aclString(); case AclCol: return seq.aclString();

View file

@ -178,10 +178,10 @@ QVariant TablesTableModel::getData(const QModelIndex &index) const
case OptionsCol: break; case OptionsCol: break;
case AclCol: return t.aclString(); case AclCol: return t.aclString();
case CommentCol: return t.description; case CommentCol: return t.description;
case TotalSizeCol: return s.oid == 0 ? QVariant() : s.totalBytes; case TotalSizeCol: return s.oid == 0 ? QVariant() : QVariant::fromValue(s.totalBytes);
case TableSizeCol: return s.oid == 0 ? QVariant() : s.totalBytes - s.indexBytes - s.toastBytes; case TableSizeCol: return s.oid == 0 ? QVariant() : QVariant::fromValue(s.totalBytes - s.indexBytes - s.toastBytes);
case IndexSizeCol: return s.oid == 0 ? QVariant() : s.indexBytes; case IndexSizeCol: return s.oid == 0 ? QVariant() : QVariant::fromValue(s.indexBytes);
case ToastSizeCol: return s.oid == 0 ? QVariant() : s.toastBytes; case ToastSizeCol: return s.oid == 0 ? QVariant() : QVariant::fromValue(s.toastBytes);
} }
return QVariant(); return QVariant();

View file

@ -4,11 +4,13 @@
#include "catalog/PgAttribute.h" #include "catalog/PgAttribute.h"
#include "catalog/PgAttributeContainer.h" #include "catalog/PgAttributeContainer.h"
#include "catalog/PgConstraintContainer.h" #include "catalog/PgConstraintContainer.h"
#include "catalog/PgClassContainer.h"
#include "SqlFormattingUtils.h" #include "SqlFormattingUtils.h"
#include "Pgsql_oids.h" #include "Pgsql_oids.h"
#include "CustomDataRole.h" #include "CustomDataRole.h"
#include <QtConcurrent> #include <QtConcurrent>
#include <QFuture> #include <QFuture>
#include <QBrush>
#include <QMessageBox> #include <QMessageBox>
#include "Pgsql_oids.h" #include "Pgsql_oids.h"
#include "Pgsql_PgException.h" #include "Pgsql_PgException.h"
@ -39,12 +41,23 @@ void CrudModel::setConfig(std::shared_ptr<OpenDatabase> db, const PgClass &table
{ {
m_database = db; m_database = db;
m_table = table; m_table = table;
m_primaryKey = db->catalog()->constraints()->getPrimaryForRelation(table.oid()); m_wholeTable = true;
callLoadData = true; callLoadData = true;
auto dbconfig = m_database->config(); auto dbconfig = m_database->config();
m_dbConn.setupConnection(dbconfig); m_dbConn.setupConnection(dbconfig);
} }
void CrudModel::setResult(std::shared_ptr<OpenDatabase> db, std::shared_ptr<Pgsql::Result> result)
{
m_database = db;
m_table.reset();
m_wholeTable = false;
callLoadData = false;
auto dbconfig = m_database->config();
m_dbConn.setupConnection(dbconfig);
loadIntoModel(result);
}
QVariant CrudModel::headerData(int section, Qt::Orientation orientation, int role) const QVariant CrudModel::headerData(int section, Qt::Orientation orientation, int role) const
{ {
if (section == 0) if (section == 0)
@ -150,7 +163,7 @@ QVariant StringToVariant(const std::string &str, Oid oid)
case Pgsql::oid_oid: case Pgsql::oid_oid:
case Pgsql::int8_oid: case Pgsql::int8_oid:
return v.operator int64_t(); return QVariant::fromValue(static_cast<int64_t>(v));
case Pgsql::bool_oid: case Pgsql::bool_oid:
return v.operator bool(); return v.operator bool();
@ -205,6 +218,8 @@ QVariant CrudModel::data(const QModelIndex &index, int role) const
if (value) if (value)
return StringToVariant(value->c_str(), typ); return StringToVariant(value->c_str(), typ);
} }
else if (role == Qt::ForegroundRole && !m_wholeTable && columnIsReadOnly(index.column()))
return QBrush(Qt::gray);
return {}; return {};
} }
@ -229,13 +244,18 @@ void CrudModel::loadData()
void CrudModel::loadIntoModel(std::shared_ptr<Pgsql::Result> data) void CrudModel::loadIntoModel(std::shared_ptr<Pgsql::Result> data)
{ {
beginResetModel(); beginResetModel();
bool was_dirty = hasPendingChanges();
m_pendingRowList.clear(); m_pendingRowList.clear();
m_roData = data; m_roData = data;
initializeColumnList(); initializeColumnList();
m_mapping.emplace(createResultSourceMapping(*m_roData, *m_database->catalog()));
lastRowKey = data->rows() - 1; lastRowKey = data->rows() - 1;
initRowMapping(); initRowMapping();
if (m_wholeTable)
appendNewRowInternal(); appendNewRowInternal();
endResetModel(); endResetModel();
if (was_dirty)
emit pendingChangesChanged(false);
} }
void CrudModel::initRowMapping() void CrudModel::initRowMapping()
@ -277,7 +297,7 @@ Qt::ItemFlags CrudModel::flags(const QModelIndex &index) const
if (index.column() < PreColumnCount) if (index.column() < PreColumnCount)
return flags; return flags;
if (m_primaryKey && !columnIsReadOnly(index.column())) if (!columnIsReadOnly(index.column()))
flags |= Qt::ItemIsEditable; flags |= Qt::ItemIsEditable;
return flags; return flags;
@ -304,7 +324,10 @@ bool CrudModel::setData(const QModelIndex &index, const QVariant &value, int rol
s.clear(); s.clear();
val = s; val = s;
} }
bool was_dirty = hasPendingChanges();
m_pendingRowList.setValue(col, row_mapping.rowKey, val); m_pendingRowList.setValue(col, row_mapping.rowKey, val);
if (!was_dirty)
emit pendingChangesChanged(true);
emit dataChanged(index, index, QVector<int>() << role); emit dataChanged(index, index, QVector<int>() << role);
return true; return true;
@ -312,22 +335,17 @@ bool CrudModel::setData(const QModelIndex &index, const QVariant &value, int rol
return false; return false;
} }
std::tuple<bool, std::vector<CrudModel::Value>> CrudModel::saveRow(const PendingRow &pending_row) std::tuple<bool, std::vector<CrudModel::Value>> CrudModel::saveRow(const PendingRow &pending_row, const RowMapping &row_mapping)
{ {
auto data = pending_row.data(); auto data = pending_row.data();
RowMapping& rowmapping = m_rowMapping[pending_row.row()]; if (data.empty())
return { true, {} };
if (!data.empty()) if (row_mapping.isNew())
{ {
QString buffer; QString buffer;
Pgsql::Params params; Pgsql::Params params;
if (rowmapping.isNew())
std::tie(buffer, params) = createInsertQuery(pending_row); std::tie(buffer, params) = createInsertQuery(pending_row);
else
{
Pgsql::Params pkey_params = getPKeyParamsForRow(pending_row.row());
std::tie(buffer, params) = createUpdateQuery(pkey_params, pending_row);
}
Pgsql::Connection db_update_conn; Pgsql::Connection db_update_conn;
auto dbconfig = m_database->config(); auto dbconfig = m_database->config();
@ -356,57 +374,118 @@ std::tuple<bool, std::vector<CrudModel::Value>> CrudModel::saveRow(const Pending
msgBox.setText(ex.what()); msgBox.setText(ex.what());
msgBox.exec(); msgBox.exec();
} }
return { false, {} };
}
return saveUpdate(pending_row, row_mapping);
}
std::tuple<bool, std::vector<CrudModel::Value>> CrudModel::saveUpdate(const PendingRow &pending_row, const RowMapping &row_mapping)
{
std::set<Oid> tables;
for (const auto &e : pending_row.data())
tables.insert(m_mapping->tableOid(e.first));
try
{
Pgsql::Connection db_update_conn;
auto dbconfig = m_database->config();
db_update_conn.connect(dbconfig.connectionString());
auto tx = Pgsql::Transaction::startTransaction(db_update_conn);
for (Oid table_oid : tables)
{
QString buffer;
Pgsql::Params params;
std::tie(buffer, params) = createUpdateQuery(table_oid, pending_row, row_mapping);
auto result = tx.queryParam(buffer, params);
if (!result || result.tuplesAffected() != 1)
{
QMessageBox::warning(nullptr, tr("Update failed"), tr("The change did not update exactly one row in %1, the row may have changed or is no longer unique.").arg(tableName(table_oid)));
return { false, {} };
}
}
tx.commit();
std::vector<Value> values;
values.reserve(m_roData->cols());
for (int col = 0; col < m_roData->cols(); ++col)
{
auto &&pending = pending_row.data();
auto found = pending.find(col);
values.push_back(found != pending.end() ? found->second : getSavedData(row_mapping, col));
}
return { true, values };
}
catch (const Pgsql::PgResultError &ex)
{
QMessageBox msgBox;
msgBox.setText(ex.what());
msgBox.exec();
} }
return { false, {} }; return { false, {} };
} }
Pgsql::Params CrudModel::getPKeyParamsForRow(int row) const QString CrudModel::tableName(Oid table_oid) const
{
auto table = m_database->catalog()->classes()->getByKey(table_oid);
return table ? table->fullyQualifiedQuotedObjectName() : QString();
}
std::vector<int> CrudModel::resultColumnsOfTable(Oid table_oid) const
{
std::vector<int> cols;
for (int col = 0; col < m_roData->cols(); ++col)
if (m_roData->ftable(col) == table_oid && m_roData->ftableCol(col) > 0)
cols.push_back(col);
return cols;
}
QString CrudModel::attributeName(int col) const
{
if (col < static_cast<int>(columnList.size()) && columnList[col])
return columnList[col]->name;
return QString::fromUtf8(m_roData->getColName(col));
}
Pgsql::Params CrudModel::getPKeyParamsForRow(const RowMapping &row_mapping, Oid table_oid) const
{ {
Pgsql::Params params; Pgsql::Params params;
for (auto attnum : m_primaryKey->key) for (int col : m_mapping->primaryKeyColumns(table_oid))
{ {
const int col = attNumToCol(attnum); auto s = getSavedData(row_mapping, col);
Oid t = getType(col); params.add(s, getType(col));
auto s = getSavedData(col, row);
params.add(s, t);
} }
return params; return params;
} }
QString CrudModel::columnName(int col) const std::tuple<QString, Pgsql::Params> CrudModel::createUpdateQuery(Oid table_oid, const PendingRow &pending_row, const RowMapping &row_mapping)
{
return m_roData->getColName(col);
}
std::tuple<QString, Pgsql::Params> CrudModel::createUpdateQuery(const Pgsql::Params &pkey_params, const PendingRow &pending_row)
{ {
Pgsql::Params params; Pgsql::Params params;
auto data = pending_row.data(); QString table_name = tableName(table_oid);
QString table_name = m_table->fullyQualifiedQuotedObjectName();
QString buffer; QString buffer;
QTextStream q(&buffer); QTextStream q(&buffer);
q << "UPDATE " << table_name << " AS d\n SET "; q << "UPDATE " << table_name << " AS d\n SET ";
int param = 0; int param = 0;
for (auto& e : data) for (auto& e : pending_row.data())
{ {
if (m_mapping->tableOid(e.first) != table_oid)
continue;
if (param > 0) if (param > 0)
q << ","; q << ",";
q << quoteIdent(columnName(e.first)) << "=$" << ++param; q << quoteIdent(attributeName(e.first)) << "=$" << ++param;
params.add(e.second, getType(e.first)); params.add(e.second, getType(e.first));
} }
q << "\nWHERE "; q << "\nWHERE ";
int i = 0; int i = 0;
for (auto attnum : m_primaryKey->key) for (int col : m_mapping->primaryKeyColumns(table_oid))
{ {
int col = attNumToCol(attnum);
if (i > 0) if (i > 0)
q << " AND "; q << " AND ";
q << quoteIdent(columnName(col)) << "=$" << ++param; q << quoteIdent(attributeName(col)) << "=$" << ++param;
++i; ++i;
} }
params.addParams(pkey_params); params.addParams(getPKeyParamsForRow(row_mapping, table_oid));
q << "\nRETURNING *";
q.flush(); q.flush();
return { buffer, params }; return { buffer, params };
} }
@ -424,12 +503,11 @@ std::tuple<QString, Pgsql::Params> CrudModel::createInsertQuery(const PendingRow
for (const auto& e : data) for (const auto& e : data)
{ {
int num = e.first; int num = e.first;
auto&& column = columnList[num];
if (first) if (first)
first = false; first = false;
else else
q << ","; q << ",";
q << quoteIdent(column.name); q << quoteIdent(attributeName(num));
} }
q << ") VALUES ($1"; q << ") VALUES ($1";
for (size_t p = 2; p <= data.size(); ++p) for (size_t p = 2; p <= data.size(); ++p)
@ -442,19 +520,6 @@ std::tuple<QString, Pgsql::Params> CrudModel::createInsertQuery(const PendingRow
return { buffer, params }; return { buffer, params };
} }
std::tuple<QString, Pgsql::Params> CrudModel::createDeleteStatement(const PKeyValues &pkey_values)
{
Pgsql::Params params;
size_t i = 0;
for (auto attnum : m_primaryKey->key)
{
const int col = attNumToCol(attnum);
params.add(pkey_values[i].c_str(), getType(col));
++i;
}
return { createDeleteStatement(), params };
}
QString CrudModel::createDeleteStatement() const QString CrudModel::createDeleteStatement() const
{ {
@ -465,12 +530,11 @@ QString CrudModel::createDeleteStatement() const
q << "DELETE FROM " << table_name; q << "DELETE FROM " << table_name;
q << "\nWHERE "; q << "\nWHERE ";
int i = 0; int i = 0;
for (auto attnum : m_primaryKey->key) for (int col : m_mapping->primaryKeyColumns(m_table->oid()))
{ {
const int col = attNumToCol(attnum);
if (i > 0) if (i > 0)
q << " AND "; q << " AND ";
q << quoteIdent(columnName(col)) << "=$" << ++i; q << quoteIdent(attributeName(col)) << "=$" << ++i;
} }
q.flush(); q.flush();
@ -483,7 +547,13 @@ bool CrudModel::savePendingChanges()
while (!m_pendingRowList.m_rows.empty()) while (!m_pendingRowList.m_rows.empty())
{ {
auto iter = m_pendingRowList.m_rows.begin(); auto iter = m_pendingRowList.m_rows.begin();
auto [ok, modified_row] = saveRow(iter->second); int pendingKey = iter->first;
auto mapping_iter = std::find_if(m_rowMapping.begin(), m_rowMapping.end(),
[pendingKey](const RowMapping &rhs) -> bool { return rhs.rowKey == pendingKey; });
if (mapping_iter == m_rowMapping.end())
return false;
auto [ok, modified_row] = saveRow(iter->second, *mapping_iter);
if (ok) if (ok)
{ {
int rowKey = iter->first; int rowKey = iter->first;
@ -516,9 +586,13 @@ bool CrudModel::columnIsReadOnly(int column_index) const
if (m_roData == nullptr) if (m_roData == nullptr)
return true; return true;
auto&& column = columnList[column_index - PreColumnCount]; int col = column_index - PreColumnCount;
return column.getIdentity() == PgAttribute::Identity::Always if (!m_mapping || !m_mapping->isEditable(col))
|| column.getGenerated() != PgAttribute::Generated::None; return true;
const auto &column = columnList[col];
return !column || column->getIdentity() == PgAttribute::Identity::Always
|| column->getGenerated() != PgAttribute::Generated::None;
} }
void CrudModel::initializeColumnList() void CrudModel::initializeColumnList()
@ -526,28 +600,51 @@ void CrudModel::initializeColumnList()
columnList.clear(); columnList.clear();
columnList.reserve(m_roData->cols()); columnList.reserve(m_roData->cols());
auto atts = m_database->catalog()->attributes(); auto atts = m_database->catalog()->attributes();
auto columns = atts->getColumnsForRelation(m_table->oid());
for (int col = 0; col < m_roData->cols(); ++col) for (int col = 0; col < m_roData->cols(); ++col)
{ {
int attnum = m_roData->ftableCol(col); int attnum = m_roData->ftableCol(col);
if (attnum <= 0)
{
columnList.emplace_back();
continue;
}
auto columns = atts->getColumnsForRelation(m_roData->ftable(col));
auto find_result = std::find_if(columns.begin(), columns.end(), [attnum](const PgAttribute &att) { return att.num == attnum; }); auto find_result = std::find_if(columns.begin(), columns.end(), [attnum](const PgAttribute &att) { return att.num == attnum; });
assert(find_result != columns.end()); if (find_result != columns.end())
columnList.push_back(*find_result); columnList.emplace_back(*find_result);
else
columnList.emplace_back();
} }
} }
bool CrudModel::submit() bool CrudModel::saveChanges()
{ {
return savePendingChanges(); bool was_dirty = hasPendingChanges();
bool ok = savePendingChanges();
if (was_dirty != hasPendingChanges())
emit pendingChangesChanged(hasPendingChanges());
return ok;
} }
void CrudModel::revert() void CrudModel::revertChanges()
{ {
if (!hasPendingChanges())
return;
m_pendingRowList.clear();
for (auto &row_mapping : m_rowMapping)
row_mapping.pending = false;
if (rowCount() > 0)
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
emit pendingChangesChanged(false);
} }
void CrudModel::appendNewRow() void CrudModel::appendNewRow()
{ {
if (!m_wholeTable)
return;
int row = static_cast<int>(m_rowMapping.size()); int row = static_cast<int>(m_rowMapping.size());
beginInsertRows(QModelIndex(), row, row); beginInsertRows(QModelIndex(), row, row);
appendNewRowInternal(); appendNewRowInternal();
@ -565,6 +662,8 @@ std::tuple<bool, QString> CrudModel::removeRows(const std::set<IntegerRange<int>
return { true, "" }; return { true, "" };
if (row_ranges.rbegin()->end() > static_cast<int>(m_rowMapping.size())) if (row_ranges.rbegin()->end() > static_cast<int>(m_rowMapping.size()))
return { false, "Range error" }; return { false, "Range error" };
if (!m_wholeTable || !m_table || m_mapping->primaryKeyColumns(m_table->oid()).empty())
return { false, "Rows can only be removed from a table with a primary key" };
// When removing rows there is no direct mapping anymore between the rows in the grid // When removing rows there is no direct mapping anymore between the rows in the grid
// and the rows in m_roData // and the rows in m_roData
@ -589,7 +688,7 @@ std::tuple<bool, QString> CrudModel::removeRows(const std::set<IntegerRange<int>
for (int current_row = range.start(); current_row < range.end(); ++current_row) for (int current_row = range.start(); current_row < range.end(); ++current_row)
{ {
auto&& mapping = m_rowMapping[static_cast<size_t>(current_row)]; auto&& mapping = m_rowMapping[static_cast<size_t>(current_row)];
auto params = getPKeyParamsForRow(mapping.rowKey); auto params = getPKeyParamsForRow(mapping, m_table->oid());
if (!params.empty()) if (!params.empty())
db_update_conn.queryParam(delete_statement, params); db_update_conn.queryParam(delete_statement, params);
} }
@ -598,7 +697,10 @@ std::tuple<bool, QString> CrudModel::removeRows(const std::set<IntegerRange<int>
// If something goes wrong after this commit we should reload contents of model // If something goes wrong after this commit we should reload contents of model
} }
// Then from model // Then from model
bool was_dirty = hasPendingChanges();
RemoveRangesOfRowsFromModel(row_ranges); RemoveRangesOfRowsFromModel(row_ranges);
if (was_dirty != hasPendingChanges())
emit pendingChangesChanged(hasPendingChanges());
return { true, "" }; return { true, "" };
} }
catch (const Pgsql::PgResultError &error) catch (const Pgsql::PgResultError &error)
@ -633,6 +735,8 @@ void CrudModel::RemoveRangeOfRowsFromModel(IntegerRange<int> range)
beginRemoveRows(QModelIndex(), range.start(), range.end() - 1); beginRemoveRows(QModelIndex(), range.start(), range.end() - 1);
SCOPE_EXIT { endRemoveRows(); }; SCOPE_EXIT { endRemoveRows(); };
auto first = m_rowMapping.begin() + range.start(); auto first = m_rowMapping.begin() + range.start();
for (auto iter = first; iter != first + range.length(); ++iter)
m_pendingRowList.m_rows.erase(iter->rowKey);
m_rowMapping.erase(first, first + range.length()); m_rowMapping.erase(first, first + range.length());
} }

View file

@ -5,8 +5,10 @@
#include "Pgsql_Connection.h" #include "Pgsql_Connection.h"
#include "IntegerRange.h" #include "IntegerRange.h"
#include "ResultSourceMapping.h"
#include "catalog/PgClass.h" #include "catalog/PgClass.h"
#include "catalog/PgConstraint.h" #include "catalog/PgConstraint.h"
#include "catalog/PgAttribute.h"
#include <QAbstractTableModel> #include <QAbstractTableModel>
#include <map> #include <map>
#include <memory> #include <memory>
@ -26,6 +28,8 @@ public:
~CrudModel() override; ~CrudModel() override;
void setConfig(std::shared_ptr<OpenDatabase> db, const PgClass &table); void setConfig(std::shared_ptr<OpenDatabase> db, const PgClass &table);
/// Show an arbitrary query result, cells are editable where the result maps them to a table with a primary key
void setResult(std::shared_ptr<OpenDatabase> db, std::shared_ptr<Pgsql::Result> result);
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
@ -42,15 +46,23 @@ public:
void loadData(); void loadData();
/// True when there are edits that have not been saved to the database
bool hasPendingChanges() const { return !m_pendingRowList.m_rows.empty(); }
/// Writes all pending rows to the database, rows that fail stay pending
bool saveChanges();
/// Discards all pending edits, the grid shows the last saved values again
void revertChanges();
std::tuple<bool, QString> removeRows(const std::set<IntegerRange<int>> &row_ranges); std::tuple<bool, QString> removeRows(const std::set<IntegerRange<int>> &row_ranges);
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
signals:
/// Emitted when the model goes from clean to having pending changes or back
void pendingChangesChanged(bool hasChanges);
public slots: public slots:
virtual bool submit() override;
virtual void revert() override;
private: private:
using PKeyValues = std::vector<std::string>;
const int PreColumnCount = 1; const int PreColumnCount = 1;
@ -76,6 +88,7 @@ private:
res += " NULLS FIRST"; res += " NULLS FIRST";
else if (nulls == NullSorting::Last) else if (nulls == NullSorting::Last)
res += " NULLS LAST"; res += " NULLS LAST";
return res;
} }
}; };
@ -205,10 +218,11 @@ private:
std::shared_ptr<OpenDatabase> m_database; std::shared_ptr<OpenDatabase> m_database;
std::optional<PgClass> m_table; std::optional<PgClass> m_table;
std::optional<PgConstraint> m_primaryKey; std::optional<ResultSourceMapping> m_mapping;
ASyncDBConnection m_dbConn; ASyncDBConnection m_dbConn;
bool callLoadData = false; bool callLoadData = false;
bool m_wholeTable = false; ///< true when showing a whole table (CRUD), false for an arbitrary query result
std::shared_ptr<Pgsql::Result> m_roData; std::shared_ptr<Pgsql::Result> m_roData;
@ -218,7 +232,7 @@ private:
// identity allways or generated // identity allways or generated
}; };
std::vector<PgAttribute> columnList; // list of columnMeta 1 to 1 with columns in m_roData. std::vector<std::optional<PgAttribute>> columnList; // attribute per column in m_roData, empty for expression columns
PendingRowList m_pendingRowList; PendingRowList m_pendingRowList;
@ -238,29 +252,30 @@ private:
Oid getType(int column) const; Oid getType(int column) const;
QString columnName(int col) const;
/// Get the PKey values from when the row was last restored or retrieved. /// Get the PKey values from when the row was last restored or retrieved.
Pgsql::Params getPKeyParamsForRow(int row) const; Pgsql::Params getPKeyParamsForRow(const RowMapping &row_mapping, Oid table_oid) const;
bool savePendingChanges(); bool savePendingChanges();
std::tuple<QString, Pgsql::Params> createUpdateQuery(const Pgsql::Params &pkey_params, const PendingRow &pending_row); std::tuple<QString, Pgsql::Params> createUpdateQuery(Oid table_oid, const PendingRow &pending_row, const RowMapping &row_mapping);
std::tuple<QString, Pgsql::Params> createInsertQuery(const PendingRow &pending_row); std::tuple<QString, Pgsql::Params> createInsertQuery(const PendingRow &pending_row);
std::tuple<QString, Pgsql::Params> createDeleteStatement(const PKeyValues &pkey_values);
QString createDeleteStatement() const; QString createDeleteStatement() const;
std::tuple<bool, std::vector<Value>> saveRow(const PendingRow &pending_row); std::tuple<bool, std::vector<Value>> saveRow(const PendingRow &pending_row, const RowMapping &row_mapping);
std::tuple<bool, std::vector<Value>> saveUpdate(const PendingRow &pending_row, const RowMapping &row_mapping);
void appendNewRow(); void appendNewRow();
void appendNewRowInternal(); void appendNewRowInternal();
int lastRowKey = -1; int lastRowKey = -1;
int allocNewRowKey() { return ++lastRowKey; } int allocNewRowKey() { return ++lastRowKey; }
/// Convert an attnum from the database catalog to the corresponding column in the model /// Name of the table with the given oid, quoted and schema qualified
/// QString tableName(Oid table_oid) const;
/// \todo still assumes columns are in order, all being shown and no special column like oid shown. /// Result columns that are user columns (attnum > 0) of the given table, in result order
int attNumToCol(int attnum) const { return attnum - 1; } std::vector<int> resultColumnsOfTable(Oid table_oid) const;
/// Catalog name of the attribute behind a result column, falls back to the result column name
QString attributeName(int col) const;
void RemoveRangesOfRowsFromModel(const std::set<IntegerRange<int>> &row_ranges); void RemoveRangesOfRowsFromModel(const std::set<IntegerRange<int>> &row_ranges);
void RemoveRangeOfRowsFromModel(IntegerRange<int> row_range); void RemoveRangeOfRowsFromModel(IntegerRange<int> row_range);

View file

@ -3,16 +3,19 @@
#include "CrudModel.h" #include "CrudModel.h"
#include "CustomDataRole.h" #include "CustomDataRole.h"
#include "ResultTableModelUtil.h" #include "ResultTableModelUtil.h"
#include "util/PgLabItemDelegate.h" #include "DataGridWidget.h"
#include "IntegerRange.h" #include "IntegerRange.h"
#include "OpenDatabase.h" #include "OpenDatabase.h"
#include "catalog/PgClassContainer.h" #include "catalog/PgClassContainer.h"
#include "catalog/PgDatabaseCatalog.h" #include "catalog/PgDatabaseCatalog.h"
#include <QDebug> #include <QDebug>
#include <QHeaderView>
#include <QMenu> #include <QMenu>
#include <QSortFilterProxyModel>
#include <QMessageBox> #include <QMessageBox>
#include <iterator> #include <iterator>
#include <set> #include <set>
#include <QTableView>
CrudTab::CrudTab(IDatabaseWindow *context, QWidget *parent) CrudTab::CrudTab(IDatabaseWindow *context, QWidget *parent)
@ -24,25 +27,18 @@ CrudTab::CrudTab(IDatabaseWindow *context, QWidget *parent)
m_db = context->openDatabase(); m_db = context->openDatabase();
SetTableViewDefault(ui->tableView);
auto delegate = new PgLabItemDelegate(ui->tableView);
ui->tableView->setItemDelegate(delegate);
m_crudModel = new CrudModel(nullptr); m_crudModel = new CrudModel(nullptr);
m_SortFilterProxy = new QSortFilterProxyModel(this); m_grid = new DataGridWidget(this);
m_SortFilterProxy->setSourceModel(m_crudModel); ui->verticalLayout->addWidget(m_grid);
m_SortFilterProxy->setSortRole(CustomSortRole); m_grid->setSourceModel(m_crudModel);
ui->tableView->setModel(m_SortFilterProxy); m_grid->view()->verticalHeader()->setVisible(false);
ui->tableView->setSortingEnabled(true);
m_SortFilterProxy->sort(0, Qt::AscendingOrder);
ui->tableView->setSelectionMode(QAbstractItemView::ExtendedSelection); m_grid->view()->setSelectionMode(QAbstractItemView::ExtendedSelection);
ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows); m_grid->view()->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->tableView->addAction(ui->actionRemove_rows); m_grid->view()->addAction(ui->actionRemove_rows);
auto horizontal_header = ui->tableView->horizontalHeader(); auto horizontal_header = m_grid->view()->horizontalHeader();
horizontal_header->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); horizontal_header->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
connect(horizontal_header, &QHeaderView::customContextMenuRequested, connect(horizontal_header, &QHeaderView::customContextMenuRequested,
this, &CrudTab::headerCustomContextMenu); this, &CrudTab::headerCustomContextMenu);
@ -64,13 +60,20 @@ void CrudTab::setConfig(Oid oid) //std::shared_ptr<OpenDatabase> db, const PgCla
void CrudTab::refresh() void CrudTab::refresh()
{ {
if (m_crudModel->hasPendingChanges()) {
auto res = QMessageBox::question(this, "pgLab",
tr("There are unsaved changes. Refreshing discards them. Do you want to continue?"),
QMessageBox::Yes, QMessageBox::No);
if (res != QMessageBox::Yes)
return;
}
m_crudModel->loadData(); m_crudModel->loadData();
} }
void CrudTab::on_actionRemove_rows_triggered() void CrudTab::on_actionRemove_rows_triggered()
{ {
std::set<IntegerRange<int>> row_ranges; std::set<IntegerRange<int>> row_ranges;
auto selection = m_SortFilterProxy->mapSelectionToSource(ui->tableView->selectionModel()->selection()); auto selection = m_grid->proxy()->mapSelectionToSource(m_grid->view()->selectionModel()->selection());
for (auto& range : selection) { for (auto& range : selection) {
row_ranges.emplace(range.top(), range.height()); row_ranges.emplace(range.top(), range.height());
} }
@ -110,7 +113,7 @@ void CrudTab::headerCustomContextMenu(const QPoint &pos)
if (headerContextMenu) if (headerContextMenu)
{ {
auto horizontal_header = ui->tableView->horizontalHeader(); auto horizontal_header = m_grid->view()->horizontalHeader();
headerContextMenu->popup(horizontal_header->mapToGlobal(pos)); headerContextMenu->popup(horizontal_header->mapToGlobal(pos));
} }
} }
@ -122,9 +125,9 @@ void CrudTab::gotoColumn(QString column)
auto name = m_crudModel->headerData(i, Qt::Horizontal).toString(); auto name = m_crudModel->headerData(i, Qt::Horizontal).toString();
if (name == column) if (name == column)
{ {
auto index = ui->tableView->currentIndex(); auto index = m_grid->view()->currentIndex();
index = ui->tableView->model()->index(index.row(), i); index = m_grid->view()->model()->index(index.row(), i);
ui->tableView->setCurrentIndex(index); m_grid->view()->setCurrentIndex(index);
break; break;
} }
} }

View file

@ -1,7 +1,6 @@
#ifndef CRUDTAB_H #ifndef CRUDTAB_H
#define CRUDTAB_H #define CRUDTAB_H
#include <QSortFilterProxyModel>
#include "catalog/PgClass.h" #include "catalog/PgClass.h"
#include "IDatabaseWindow.h" #include "IDatabaseWindow.h"
#include <QWidget> #include <QWidget>
@ -14,6 +13,7 @@ namespace Ui {
class OpenDatabase; class OpenDatabase;
class CrudModel; class CrudModel;
class DataGridWidget;
class QMenu; class QMenu;
class CrudTab : public QWidget { class CrudTab : public QWidget {
@ -33,7 +33,7 @@ private:
std::optional<PgClass> m_table; std::optional<PgClass> m_table;
CrudModel *m_crudModel = nullptr; CrudModel *m_crudModel = nullptr;
QSortFilterProxyModel *m_SortFilterProxy = nullptr; DataGridWidget *m_grid = nullptr;
QMenu *headerContextMenu = nullptr; QMenu *headerContextMenu = nullptr;

View file

@ -13,15 +13,7 @@
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout"/>
<item>
<widget class="QTableView" name="tableView">
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
<action name="actionRemove_rows"> <action name="actionRemove_rows">
<property name="text"> <property name="text">
<string>Remove rows</string> <string>Remove rows</string>

View file

@ -0,0 +1,101 @@
#include "DataGridWidget.h"
#include "CrudModel.h"
#include "CustomDataRole.h"
#include "ResultTableModelUtil.h"
#include "util/PgLabItemDelegate.h"
#include <QAction>
#include <QSortFilterProxyModel>
#include <QStyle>
#include <QTableView>
#include <QToolBar>
#include <QVBoxLayout>
DataGridWidget::DataGridWidget(QWidget *parent)
: QWidget(parent)
{
auto layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
m_toolbar = new QToolBar(this);
m_toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
layout->addWidget(m_toolbar);
m_actSave = new QAction(style()->standardIcon(QStyle::SP_DialogSaveButton), tr("Save"), this);
m_actSave->setShortcut(QKeySequence::Save);
m_actSave->setShortcutContext(Qt::WidgetWithChildrenShortcut);
m_actSave->setToolTip(tr("Save all pending changes to the database (Ctrl+S)"));
m_actSave->setEnabled(false);
addAction(m_actSave);
m_toolbar->addAction(m_actSave);
connect(m_actSave, &QAction::triggered, this, &DataGridWidget::onSave);
m_actRevert = new QAction(style()->standardIcon(QStyle::SP_DialogResetButton), tr("Revert"), this);
m_actRevert->setToolTip(tr("Discard all pending changes"));
m_actRevert->setEnabled(false);
m_toolbar->addAction(m_actRevert);
connect(m_actRevert, &QAction::triggered, this, &DataGridWidget::onRevert);
m_view = new QTableView(this);
layout->addWidget(m_view);
SetTableViewDefault(m_view);
auto delegate = new PgLabItemDelegate(m_view);
m_view->setItemDelegate(delegate);
}
void DataGridWidget::setSourceModel(QAbstractItemModel *model)
{
if (!m_proxy) {
m_proxy = new QSortFilterProxyModel(this);
m_proxy->setSortRole(CustomSortRole);
}
disconnect(m_pendingChangesConnection);
m_proxy->setSourceModel(model);
m_view->setModel(m_proxy);
m_view->setSortingEnabled(true);
m_proxy->sort(0, Qt::AscendingOrder);
auto crud = qobject_cast<CrudModel *>(model);
m_toolbar->setVisible(crud != nullptr);
if (crud) {
m_pendingChangesConnection = connect(crud, &CrudModel::pendingChangesChanged, this, &DataGridWidget::onPendingChangesChanged);
onPendingChangesChanged(crud->hasPendingChanges());
}
else {
onPendingChangesChanged(false);
}
}
void DataGridWidget::onPendingChangesChanged(bool hasChanges)
{
m_actSave->setEnabled(hasChanges);
m_actRevert->setEnabled(hasChanges);
}
void DataGridWidget::onSave()
{
auto crud = qobject_cast<CrudModel *>(m_proxy->sourceModel());
if (!crud)
return;
commitOpenEditor();
crud->saveChanges();
}
void DataGridWidget::onRevert()
{
auto crud = qobject_cast<CrudModel *>(m_proxy->sourceModel());
if (!crud)
return;
commitOpenEditor();
crud->revertChanges();
}
void DataGridWidget::commitOpenEditor()
{
// Moving focus away from the editor makes the delegate commit its value, a toolbar click does not take focus
if (m_view->indexWidget(m_view->currentIndex()))
m_view->setFocus();
}

View file

@ -0,0 +1,42 @@
#ifndef DATAGRIDWIDGET_H
#define DATAGRIDWIDGET_H
#include <QWidget>
class QAbstractItemModel;
class QAction;
class QSortFilterProxyModel;
class QTableView;
class QToolBar;
/** Sortable and editable grid shared by the CRUD tab and the query result tab.
*/
class DataGridWidget : public QWidget {
Q_OBJECT
public:
explicit DataGridWidget(QWidget *parent = nullptr);
/// Sets the model to show, sorting is done by a proxy that uses CustomSortRole
void setSourceModel(QAbstractItemModel *model);
QTableView *view() const { return m_view; }
QSortFilterProxyModel *proxy() const { return m_proxy; }
private slots:
void onPendingChangesChanged(bool hasChanges);
void onSave();
void onRevert();
private:
/// Closes an open cell editor so its value is written to the model before saving or reverting
void commitOpenEditor();
QTableView *m_view = nullptr;
QSortFilterProxyModel *m_proxy = nullptr;
QToolBar *m_toolbar = nullptr;
QAction *m_actSave = nullptr;
QAction *m_actRevert = nullptr;
QMetaObject::Connection m_pendingChangesConnection;
};
#endif // DATAGRIDWIDGET_H

View file

@ -1,229 +0,0 @@
#-------------------------------------------------
#
# Project created by QtCreator 2016-11-11T09:17:13
#
#-------------------------------------------------
QT += core gui concurrent
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets sql
TARGET = pglab
TEMPLATE = app
! include( ../common.pri ) {
error( "Couldn't find the common.pri file!" )
}
DEFINES += _WIN32_WINNT=0x0501
win32:RC_ICONS += pglab.ico
SOURCES += main.cpp\
About.cpp \
catalog/delegates/IconColumnDelegate.cpp \
catalog/models/BaseTableModel.cpp \
catalog/models/ColumnTableModel.cpp \
catalog/models/ConstraintModel.cpp \
catalog/models/DatabasesTableModel.cpp \
catalog/models/DependantsTableModel.cpp \
catalog/models/IndexModel.cpp \
catalog/models/ProcTableModel.cpp \
catalog/models/RolesTableModel.cpp \
catalog/models/SequenceModel.cpp \
catalog/models/TriggerTableModel.cpp \
catalog/tables/TablesTableModel.cpp \
catalog/widgets/CatalogConstraintPage.cpp \
catalog/widgets/CatalogFunctionsPage.cpp \
catalog/widgets/CatalogIndexPage.cpp \
catalog/widgets/CatalogInspector.cpp \
catalog/widgets/CatalogNamespacePage.cpp \
catalog/widgets/CatalogPageBase.cpp \
catalog/widgets/CatalogSequencesPage.cpp \
catalog/widgets/CatalogTablesPage.cpp \
catalog/widgets/CatalogTypesPage.cpp \
catalog/widgets/ColumnPage.cpp \
catalog/widgets/DependantsPage.cpp \
catalog/widgets/TriggerPage.cpp \
codeeditor/CodeEditor.cpp \
codeeditor/EditorGutter.cpp \
codeeditor/GutterPainter.cpp \
crud/CrudModel.cpp \
crud/CrudTab.cpp \
querytool/QueryExplainModel.cpp \
querytool/QueryParamListController.cpp \
querytool/QueryResultModel.cpp \
querytool/QueryTool.cpp \
serverinspector/DatabasesPage.cpp \
serverinspector/RolesPage.cpp \
serverinspector/ServerInspector.cpp \
util/Colors.cpp \
util/PgLabItemDelegate.cpp \
util/PgLabTableView.cpp \
util/SqlSyntaxHighlighter.cpp \
ConnectionConfigurationWidget.cpp \
ConnectionController.cpp \
ManagedPage.cpp \
PgDumpOutputHighlighter.cpp \
CreateDatabaseDialog.cpp \
ConnectionManagerWindow.cpp \
ConnectionListModel.cpp \
SslModeModel.cpp \
stopwatch.cpp \
TuplesResultWidget.cpp \
BackupDialog.cpp \
MasterController.cpp \
ParamTypeDelegate.cpp \
OpenDatabase.cpp \
ProcessStdioWidget.cpp \
ResultTableModelUtil.cpp \
NamespaceItemModel.cpp \
PropertyProxyModel.cpp \
CodeGenerator.cpp \
UserConfiguration.cpp \
widgets/SqlCodePreview.cpp \
CustomFilterSortModel.cpp \
PropertiesPage.cpp \
PasswordPromptDialog.cpp \
EditTableWidget.cpp \
EditColumnTableModel.cpp \
DatabaseWindow.cpp \
widgets/SingleRecordModel.cpp \
widgets/SingleRecordWidget.cpp
HEADERS += \
About.h \
Config.h \
catalog/delegates/IconColumnDelegate.h \
catalog/models/BaseTableModel.h \
catalog/models/ColumnTableModel.h \
catalog/models/ConstraintModel.h \
catalog/models/DatabasesTableModel.h \
catalog/models/DependantsTableModel.h \
catalog/models/IndexModel.h \
catalog/models/ProcTableModel.h \
catalog/models/RolesTableModel.h \
catalog/models/SequenceModel.h \
catalog/models/TriggerTableModel.h \
catalog/tables/TablesTableModel.h \
catalog/widgets/CatalogConstraintPage.h \
catalog/widgets/CatalogFunctionsPage.h \
catalog/widgets/CatalogIndexPage.h \
catalog/widgets/CatalogInspector.h \
catalog/widgets/CatalogNamespacePage.h \
catalog/widgets/CatalogPageBase.h \
catalog/widgets/CatalogSequencesPage.h \
catalog/widgets/CatalogTablesPage.h \
catalog/widgets/CatalogTypesPage.h \
catalog/widgets/ColumnPage.h \
catalog/widgets/DependantsPage.h \
catalog/widgets/TriggerPage.h \
codeeditor/CodeEditor.h \
codeeditor/EditorGutter.h \
codeeditor/GutterPainter.h \
crud/CrudModel.h \
crud/CrudTab.h \
querytool/QueryExplainModel.h \
querytool/QueryParamListController.h \
querytool/QueryResultModel.h \
querytool/QueryTool.h \
serverinspector/DatabasesPage.h \
serverinspector/RolesPage.h \
serverinspector/ServerInspector.h \
util/Colors.h \
util/PgLabItemDelegate.h \
util/PgLabTableView.h \
util/PgLabTableViewHelper.h \
util/SqlSyntaxHighlighter.h \
ConnectionConfigurationWidget.h \
ConnectionController.h \
IDatabaseWindow.h \
ManagedPage.h \
PgDumpOutputHighlighter.h \
CreateDatabaseDialog.h \
ConnectionManagerWindow.h \
ConnectionListModel.h \
SslModeModel.h \
stopwatch.h \
TuplesResultWidget.h \
BackupDialog.h \
MasterController.h \
ParamTypeDelegate.h \
OpenDatabase.h \
ProcessStdioWidget.h \
ResultTableModelUtil.h \
NamespaceItemModel.h \
AbstractCommand.h \
PropertyProxyModel.h \
CustomDataRole.h \
CodeGenerator.h \
UserConfiguration.h \
widgets/SqlCodePreview.h \
CustomFilterSortModel.h \
PropertiesPage.h \
PasswordPromptDialog.h \
EditTableWidget.h \
EditColumnTableModel.h \
DatabaseWindow.h \
NamespaceFilter.h \
widgets/SingleRecordModel.h \
widgets/SingleRecordWidget.h
FORMS += \
ConnectionManagerWindow.ui \
CreateDatabaseDialog.ui \
DatabaseWindow.ui \
TuplesResultWidget.ui \
querytool/QueryTab.ui \
ProcessStdioWidget.ui \
crud/CrudTab.ui \
CodeGenerator.ui \
widgets/SingleRecordWidget.ui
RESOURCES += \
resources.qrc
#QMAKE_LFLAGS_WINDOWS = /SUBSYSTEM:WINDOWS,5.01
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../core/release/ -lcore
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../core/debug/ -lcore
INCLUDEPATH += $$PWD/../core
DEPENDPATH += $$PWD/../core
win32:CONFIG(debug, debug|release): LIBS += -lbotand
else:win32:CONFIG(release, debug|release): LIBS += -lbotan
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/libcore.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/libcore.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/core.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/core.lib
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../pgsql/release/ -lpgsql
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../pgsql/debug/ -lpgsql
else:unix:!macx: LIBS += -L$$OUT_PWD/../pgsql/ -lpgsql
INCLUDEPATH += $$PWD/../pgsql
DEPENDPATH += $$PWD/../pgsql
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pgsql/release/libpgsql.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pgsql/debug/libpgsql.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pgsql/release/pgsql.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pgsql/debug/pgsql.lib
else:unix:!macx: PRE_TARGETDEPS += $$OUT_PWD/../pgsql/libpgsql.a
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../pglablib/release/ -lpglablib
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../pglablib/debug/ -lpglablib
else:unix:!macx: LIBS += -L$$OUT_PWD/../pglablib/ -lpglablib
INCLUDEPATH += $$PWD/../pglablib
DEPENDPATH += $$PWD/../pglablib
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pglablib/release/libpglablib.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pglablib/debug/libpglablib.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pglablib/release/pglablib.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pglablib/debug/pglablib.lib
else:unix:!macx: PRE_TARGETDEPS += $$OUT_PWD/../pglablib/libpglablib.a

View file

@ -40,7 +40,7 @@ if (role == Qt::DisplayRole) {
result = item->estimateError(); result = item->estimateError();
break; break;
case c_ColumnRowCount: case c_ColumnRowCount:
result = item->actualRows; result = QVariant::fromValue(item->actualRows);
break; break;
case c_ColumnLoops: case c_ColumnLoops:
result = item->actualLoops; result = item->actualLoops;

View file

@ -1,85 +0,0 @@
#include "QueryResultModel.h"
#include "ResultTableModelUtil.h"
#include "Pgsql_declare.h"
#include "Pgsql_oids.h"
#include "catalog/PgDatabaseCatalog.h"
#include "CustomDataRole.h"
#include <QBrush>
#include <QColor>
using namespace Pgsql;
QueryResultModel::QueryResultModel(QObject *parent, std::shared_ptr<Pgsql::Result> r
, std::shared_ptr<PgDatabaseCatalog> cat)
: QAbstractTableModel(parent)
, result(std::move(r))
, catalog(cat)
{}
int QueryResultModel::rowCount(const QModelIndex &) const
{
int r = result->rows();
return r;
}
int QueryResultModel::columnCount(const QModelIndex &) const
{
int r = result->cols();
return r;
}
QVariant QueryResultModel::data(const QModelIndex &index, int role) const
{
// static const QString null_str("null");
QVariant v;
const int col = index.column();
const int rij = index.row();
const Oid oid = result->type(col);
if (role == Qt::DisplayRole || role == Qt::EditRole) {
if (result->null(col, rij)) {
//v = nullptr;
}
else {
Value value = result->get(col, rij);
if (oid == bool_oid) {
bool b = value;
v = b;
}
else {
QString s = value;
v = s;
}
}
}
else if (role == CustomDataTypeRole) {
v = oid;
}
return v;
}
QVariant QueryResultModel::headerData(int section, Qt::Orientation orientation, int role) const
{
QVariant r;
if (role == Qt::DisplayRole) {
if (orientation == Qt::Horizontal) {
QString s(result->getColName(section));
s += "\n";
s += getTypeDisplayString(*catalog, result->type(section));
r = s;
}
else {
r = QString::number(section + 1);
}
}
Qt::ItemFlags f;
return r;
}
Qt::ItemFlags QueryResultModel::flags(const QModelIndex &) const
{
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
}

View file

@ -1,31 +0,0 @@
#ifndef QUERYRESULTMODEL_H
#define QUERYRESULTMODEL_H
#include <QAbstractTableModel>
#include "catalog/models/BaseTableModel.h"
#include "Pgsql_Connection.h"
class PgDatabaseCatalog;
class QueryResultModel : public QAbstractTableModel
{
Q_OBJECT
public:
QueryResultModel(QObject *parent, std::shared_ptr<Pgsql::Result> r, std::shared_ptr<PgDatabaseCatalog> cat);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
std::shared_ptr<const Pgsql::Result> GetPgsqlResult() const { return result; }
protected:
// virtual Oid getType(int column) const override;
// virtual QVariant getData(const QModelIndex &index) const override;
private:
std::shared_ptr<Pgsql::Result> result;
std::shared_ptr<PgDatabaseCatalog> catalog;
};
#endif // QUERYRESULTMODEL_H

View file

@ -476,10 +476,8 @@ void QueryTool::query_ready(std::shared_ptr<Pgsql::Result> dbres, qint64 elapsed
//int n_rows = dbres->getRows(); //int n_rows = dbres->getRows();
//QString rowcount_str = QString("rows: %1").arg(dbres->getRows()); //QString rowcount_str = QString("rows: %1").arg(dbres->getRows());
auto result_model = std::make_shared<QueryResultModel>(nullptr , dbres,
m_catalog);
TuplesResultWidget *trw = new TuplesResultWidget; TuplesResultWidget *trw = new TuplesResultWidget;
trw->setResult(result_model, elapsedms); trw->setResult(m_context->openDatabase(), dbres, elapsedms);
resultList.push_back(trw); resultList.push_back(trw);
ui->tabWidget->addTab(trw, "Data"); ui->tabWidget->addTab(trw, "Data");
if (resultList.size() == 1) if (resultList.size() == 1)

View file

@ -3,7 +3,6 @@
#include "ManagedPage.h" #include "ManagedPage.h"
#include "ASyncDBConnection.h" #include "ASyncDBConnection.h"
#include "QueryResultModel.h"
#include "QueryExplainModel.h" #include "QueryExplainModel.h"
#include "stopwatch.h" #include "stopwatch.h"
#include "tuplesresultwidget.h" #include "tuplesresultwidget.h"
@ -26,7 +25,6 @@ class QTableView;
class QTabWidget; class QTabWidget;
class SqlSyntaxHighlighter; class SqlSyntaxHighlighter;
class ExplainRoot; class ExplainRoot;
class QueryResultModel;
class QueryExplainModel; class QueryExplainModel;
class PgTypeContainer; class PgTypeContainer;
class OpenDatabase; class OpenDatabase;

View file

@ -1,11 +1,15 @@
#include "tuplesresultwidget.h" #include "tuplesresultwidget.h"
#include "ui_tuplesresultwidget.h" #include "ui_tuplesresultwidget.h"
#include "ResultTableModelUtil.h" #include "ResultTableModelUtil.h"
#include "util.h" #include "util.h"
#include "crud/CrudModel.h"
#include "crud/DataGridWidget.h"
#include "OpenDatabase.h"
#include <QFile> #include <QFile>
#include <QTextStream> #include <QTextStream>
#include "util/PgLabItemDelegate.h" #include <QTableView>
#include <QHeaderView>
TuplesResultWidget::TuplesResultWidget(QWidget *parent) : TuplesResultWidget::TuplesResultWidget(QWidget *parent) :
@ -15,9 +19,13 @@ TuplesResultWidget::TuplesResultWidget(QWidget *parent) :
ui->setupUi(this); ui->setupUi(this);
ui->lblRowCount->setText(QString()); ui->lblRowCount->setText(QString());
SetTableViewDefault(ui->ResultView); m_grid = new DataGridWidget(this);
auto delegate = new PgLabItemDelegate(ui->ResultView); ui->verticalLayout->insertWidget(0, m_grid);
ui->ResultView->setItemDelegate(delegate); m_grid->view()->setFont(QFont("Source Sans Pro", 10));
m_grid->view()->setAlternatingRowColors(true);
m_grid->view()->setWordWrap(false);
m_grid->view()->verticalHeader()->setDefaultSectionSize(21);
m_grid->view()->verticalHeader()->setMinimumSectionSize(16);
} }
TuplesResultWidget::~TuplesResultWidget() TuplesResultWidget::~TuplesResultWidget()
@ -25,14 +33,17 @@ TuplesResultWidget::~TuplesResultWidget()
delete ui; delete ui;
} }
void TuplesResultWidget::setResult(std::shared_ptr<QueryResultModel> res, float ms) void TuplesResultWidget::setResult(std::shared_ptr<OpenDatabase> db, std::shared_ptr<Pgsql::Result> res, float ms)
{ {
resultModel = res; m_result = res;
ui->ResultView->setModel(resultModel.get());
ui->ResultView->resizeColumnsToContents(); m_model = new CrudModel(m_grid);
m_model->setResult(db, res);
m_grid->setSourceModel(m_model);
QString rowcount_str = QString("rows: %1").arg(resultModel->rowCount()); m_grid->view()->resizeColumnsToContents();
QString rowcount_str = QString("rows: %1").arg(m_model->rowCount());
ui->lblRowCount->setText(rowcount_str); ui->lblRowCount->setText(rowcount_str);
ui->lblElapsedTime->setText(msfloatToHumanReadableString(ms)); ui->lblElapsedTime->setText(msfloatToHumanReadableString(ms));
} }
@ -42,8 +53,6 @@ void TuplesResultWidget::exportData(const QString &file_name) const
QFile file(file_name); QFile file(file_name);
if (file.open(QIODevice::WriteOnly)) { if (file.open(QIODevice::WriteOnly)) {
QTextStream out(&file); QTextStream out(&file);
::exportTable(ui->ResultView, out); ::exportTable(m_grid->view(), out);
} }
} }

View file

@ -1,13 +1,21 @@
#ifndef TUPLESRESULTWIDGET_H #ifndef TUPLESRESULTWIDGET_H
#define TUPLESRESULTWIDGET_H #define TUPLESRESULTWIDGET_H
#include "querytool/QueryResultModel.h"
#include <QWidget> #include <QWidget>
#include <memory>
namespace Ui { namespace Ui {
class TuplesResultWidget; class TuplesResultWidget;
} }
namespace Pgsql {
class Result;
}
class OpenDatabase;
class CrudModel;
class DataGridWidget;
class TuplesResultWidget : public QWidget class TuplesResultWidget : public QWidget
{ {
Q_OBJECT Q_OBJECT
@ -16,14 +24,15 @@ public:
explicit TuplesResultWidget(QWidget *parent = 0); explicit TuplesResultWidget(QWidget *parent = 0);
~TuplesResultWidget(); ~TuplesResultWidget();
void setResult(std::shared_ptr<QueryResultModel> res, float ms); void setResult(std::shared_ptr<OpenDatabase> db, std::shared_ptr<Pgsql::Result> res, float ms);
void exportData(const QString &file_name) const; void exportData(const QString &file_name) const;
std::shared_ptr<const Pgsql::Result> GetPgsqlResult() const { return resultModel->GetPgsqlResult(); } std::shared_ptr<const Pgsql::Result> GetPgsqlResult() const { return m_result; }
private: private:
Ui::TuplesResultWidget *ui; Ui::TuplesResultWidget *ui;
DataGridWidget *m_grid = nullptr;
std::shared_ptr<QueryResultModel> resultModel; CrudModel *m_model = nullptr;
std::shared_ptr<Pgsql::Result> m_result;
}; };
#endif // TUPLESRESULTWIDGET_H #endif // TUPLESRESULTWIDGET_H

View file

@ -29,37 +29,6 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>2</number> <number>2</number>
</property> </property>
<item>
<widget class="QTableView" name="ResultView">
<property name="font">
<font>
<family>Source Sans Pro</family>
<pointsize>10</pointsize>
</font>
</property>
<property name="editTriggers">
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
<attribute name="verticalHeaderDefaultSectionSize">
<number>21</number>
</attribute>
<attribute name="verticalHeaderMinimumSectionSize">
<number>16</number>
</attribute>
</widget>
</item>
<item> <item>
<widget class="QWidget" name="widget" native="true"> <widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">

View file

@ -1,18 +0,0 @@
TEMPLATE = subdirs
DEFINES += BOOST_ENABLE_ASSERT_HANDLER
SUBDIRS += core \
pgsql \
pglablib \
pglab
pglab.depends = core pgsql pglablib
pgsql.depends = core
pglablib.depends = core pgsql
tests.depends = core pgsql pglablib
CONFIG(debug, debug|release) {
SUBDIRS += tests
}

View file

@ -1,13 +1,14 @@
#include "ASyncDBConnection.h" #include "ASyncDBConnection.h"
#include "ScopeGuard.h" #include "ScopeGuard.h"
#include "util.h" #include "util.h"
#include "Pgsql_PgException.h" #include "Pgsql_PgException.h"
#include "Win32Event.h" #include <atomic>
#include "WaitHandleList.h"
#include <chrono> #include <chrono>
#include <queue> #include <queue>
#include <WinSock2.h>
#include <QDebug> #include <QDebug>
#include <QEventLoop>
#include <QSocketNotifier>
#include <QTimer>
namespace { namespace {
@ -45,10 +46,6 @@ public:
struct t_Command { struct t_Command {
std::mutex m_mutex; std::mutex m_mutex;
t_CommandQueue m_queue; t_CommandQueue m_queue;
Win32Event m_newEvent;
t_Command()
: m_newEvent(Win32Event::Reset::Auto, Win32Event::Initial::Clear)
{}
} m_commandQueue; } m_commandQueue;
ConnectionConfig m_config; ConnectionConfig m_config;
@ -64,18 +61,29 @@ public:
void stop(); void stop();
/// Wakes the worker thread when it is waiting in its event loop
void wake();
private: private:
enum class WaitResult {
Socket, ///< the socket became ready
Wakeup, ///< woken up by stop() or by a new command
Timeout ///< the timeout expired
};
ASyncDBConnection *asyncConnObject = nullptr; ASyncDBConnection *asyncConnObject = nullptr;
/// \todo Implement new method to stop the thread std::atomic<bool> terminateRequested{false}; ///< is set when the thread should stop
Win32Event m_stopEvent; std::mutex m_wakeMutex;
QEventLoop *m_loop = nullptr; ///< loop the worker blocks in, guarded by m_wakeMutex
Pgsql::Connection m_connection; Pgsql::Connection m_connection;
bool terminateRequested = false; ///< is set when the thread should stop
bool m_terminated = true; bool m_terminated = true;
Pgsql::Canceller m_canceller; Pgsql::Canceller m_canceller;
QElapsedTimer m_timer; QElapsedTimer m_timer;
bool hasQueuedCommand();
WaitResult waitForSocket(QSocketNotifier::Type type, int timeout_ms, bool wakeOnNewCommand);
bool makeConnection(); bool makeConnection();
void communicate(); void communicate();
@ -97,7 +105,6 @@ private:
ASyncDBConnectionThread::ASyncDBConnectionThread(ASyncDBConnection *asco) ASyncDBConnectionThread::ASyncDBConnectionThread(ASyncDBConnection *asco)
: asyncConnObject(asco) : asyncConnObject(asco)
, m_stopEvent(Win32Event::Reset::Manual, Win32Event::Initial::Clear)
{} {}
void ASyncDBConnectionThread::run() void ASyncDBConnectionThread::run()
@ -154,6 +161,59 @@ bool ASyncDBConnectionThread::cancel()
return m_canceller.cancel(nullptr); return m_canceller.cancel(nullptr);
} }
bool ASyncDBConnectionThread::hasQueuedCommand()
{
std::lock_guard<std::mutex> lg(m_commandQueue.m_mutex);
return !m_commandQueue.m_queue.empty();
}
ASyncDBConnectionThread::WaitResult ASyncDBConnectionThread::waitForSocket(QSocketNotifier::Type type, int timeout_ms, bool wakeOnNewCommand)
{
QEventLoop loop;
WaitResult result = WaitResult::Wakeup;
QSocketNotifier notifier(m_connection.socket(), type);
QObject::connect(&notifier, &QSocketNotifier::activated, &loop, [&result, &loop] {
result = WaitResult::Socket;
loop.quit();
});
QTimer timer;
timer.setSingleShot(true);
if (timeout_ms >= 0) {
QObject::connect(&timer, &QTimer::timeout, &loop, [&result, &loop] {
result = WaitResult::Timeout;
loop.quit();
});
timer.start(timeout_ms);
}
{
std::lock_guard<std::mutex> lg(m_wakeMutex);
m_loop = &loop;
}
// Checked after publishing the loop, so a wake-up that happened before is not lost
bool pending = terminateRequested || (wakeOnNewCommand && hasQueuedCommand());
if (!pending) {
loop.exec();
}
{
std::lock_guard<std::mutex> lg(m_wakeMutex);
m_loop = nullptr;
}
return result;
}
void ASyncDBConnectionThread::wake()
{
std::lock_guard<std::mutex> lg(m_wakeMutex);
if (m_loop) {
QMetaObject::invokeMethod(m_loop, [loop = m_loop] { loop->quit(); }, Qt::QueuedConnection);
}
}
bool ASyncDBConnectionThread::makeConnection() bool ASyncDBConnectionThread::makeConnection()
{ {
using namespace std::literals::chrono_literals; using namespace std::literals::chrono_literals;
@ -165,30 +225,14 @@ bool ASyncDBConnectionThread::makeConnection()
bool ok = m_connection.connectStart(conn_string); //keywords, values); bool ok = m_connection.connectStart(conn_string); //keywords, values);
auto start = std::chrono::steady_clock::now(); auto start = std::chrono::steady_clock::now();
if (ok && m_connection.status() != CONNECTION_BAD) { if (ok && m_connection.status() != CONNECTION_BAD) {
Win32Event socket_event(Win32Event::Reset::Auto, Win32Event::Initial::Clear); QSocketNotifier::Type type = QSocketNotifier::Write;
long fd = FD_WRITE;
while (true) { while (true) {
// poll till complete or failed (we can get an abort command) // poll till complete or failed (we can get an abort command)
int sock = m_connection.socket(); auto elapsed = std::chrono::steady_clock::now() - start;
WSAEventSelect(sock, socket_event.handle(), fd); auto remaining = std::chrono::duration_cast<std::chrono::milliseconds>(30s - elapsed).count();
WaitHandleList whl; WaitResult res = waitForSocket(type, remaining > 0 ? static_cast<int>(remaining) : 0, false);
auto wait_result_socket_event = whl.add(socket_event);
auto wait_result_stop = whl.add(m_stopEvent);
auto nu = std::chrono::steady_clock::now(); if (res == WaitResult::Socket) {
std::chrono::duration<float, std::milli> diff = -(nu-start);
diff += 30s;
DWORD timeout = diff.count();
DWORD res = MsgWaitForMultipleObjectsEx(
whl.count(), // _In_ DWORD nCount,
whl, // _In_ const HANDLE *pHandles,
timeout, // _In_ DWORD dwMilliseconds,
0, // _In_ DWORD dwWakeMask,
0 // _In_ DWORD dwFlags
);
if (res == wait_result_socket_event) {
auto poll_state = m_connection.connectPoll(); auto poll_state = m_connection.connectPoll();
if (poll_state == PGRES_POLLING_OK) { if (poll_state == PGRES_POLLING_OK) {
qDebug() << "ASyncDBConnection Connected"; qDebug() << "ASyncDBConnection Connected";
@ -202,17 +246,21 @@ bool ASyncDBConnectionThread::makeConnection()
} }
else if (poll_state == PGRES_POLLING_READING) { else if (poll_state == PGRES_POLLING_READING) {
doStateCallback({ ASyncDBConnection::State::Connecting, "Negotiating" }); doStateCallback({ ASyncDBConnection::State::Connecting, "Negotiating" });
fd = FD_READ; type = QSocketNotifier::Read;
} }
else if (poll_state == PGRES_POLLING_WRITING) { else if (poll_state == PGRES_POLLING_WRITING) {
doStateCallback({ ASyncDBConnection::State::Connecting, "Negotiating" }); doStateCallback({ ASyncDBConnection::State::Connecting, "Negotiating" });
fd = FD_WRITE; type = QSocketNotifier::Write;
} }
} }
else if (res == wait_result_stop) { else if (terminateRequested) {
m_connection.close();
return false;
}
else if (res == WaitResult::Timeout) {
doStateCallback({ ASyncDBConnection::State::NotConnected, "Connection timed out" });
m_connection.close(); m_connection.close();
return false; return false;
} }
} // end while (true) } // end while (true)
} }
@ -252,7 +300,7 @@ void ASyncDBConnectionThread::communicate()
void ASyncDBConnectionThread::stop() void ASyncDBConnectionThread::stop()
{ {
terminateRequested = true; terminateRequested = true;
m_stopEvent.set(); wake();
} }
void ASyncDBConnectionThread::doStateCallback(ASyncDBConnection::StateData state) void ASyncDBConnectionThread::doStateCallback(ASyncDBConnection::StateData state)
@ -264,30 +312,14 @@ void ASyncDBConnectionThread::doStateCallback(ASyncDBConnection::StateData state
bool ASyncDBConnectionThread::waitForAndSendCommand() bool ASyncDBConnectionThread::waitForAndSendCommand()
{ {
SOCKET sock = static_cast<SOCKET>(m_connection.socket()); if (hasQueuedCommand()) {
Win32Event socket_event(Win32Event::Reset::Manual, Win32Event::Initial::Clear);
long fd = FD_READ | FD_CLOSE;
WSAEventSelect(sock, socket_event.handle(), fd);
WaitHandleList whl;
auto wait_result_new_command = whl.add(m_commandQueue.m_newEvent);
auto wait_result_socket = whl.add(socket_event);
whl.add(m_stopEvent);
DWORD res = MsgWaitForMultipleObjectsEx(
whl.count(), // _In_ DWORD nCount,
whl, // _In_ const HANDLE *pHandles,
INFINITE, // _In_ DWORD dwMilliseconds,
0, // _In_ DWORD dwWakeMask,
0 // _In_ DWORD dwFlags
);
if (res == wait_result_new_command) {
doNewCommand(); doNewCommand();
return true;
} }
else if (res == wait_result_socket) {
WSANETWORKEVENTS net_events; // Idle connection: a readable socket carries notifies or notices, or signals a lost connection
WSAEnumNetworkEvents(sock, socket_event.handle(), &net_events); if (waitForSocket(QSocketNotifier::Read, -1, true) == WaitResult::Socket) {
if (net_events.lNetworkEvents & FD_CLOSE) { if (!m_connection.consumeInput()) {
doStateCallback({ ASyncDBConnection::State::NotConnected, "Connection lost" }); doStateCallback({ ASyncDBConnection::State::NotConnected, "Connection lost" });
return false; return false;
} }
@ -347,42 +379,16 @@ bool ASyncDBConnectionThread::consumeResultInput()
void ASyncDBConnectionThread::waitForResult() void ASyncDBConnectionThread::waitForResult()
{ {
SOCKET sock = static_cast<SOCKET>(m_connection.socket());
Win32Event socket_event(Win32Event::Reset::Manual, Win32Event::Initial::Clear);
long fd = FD_READ | FD_CLOSE;
bool finished = false; bool finished = false;
while ( ! finished) { while ( ! finished) {
WSAEventSelect(sock, socket_event.handle(), fd); WaitResult res = waitForSocket(QSocketNotifier::Read, -1, false);
WaitHandleList whl; if (res == WaitResult::Socket) {
auto wait_result_socket = whl.add(socket_event);
auto wait_result_stop = whl.add(m_stopEvent);
DWORD res = MsgWaitForMultipleObjectsEx(
whl.count(), // _In_ DWORD nCount,
whl, // _In_ const HANDLE *pHandles,
INFINITE, // _In_ DWORD dwMilliseconds,
0, // _In_ DWORD dwWakeMask,
0 // _In_ DWORD dwFlags
);
if (res == wait_result_socket) {
WSANETWORKEVENTS net_events;
WSAEnumNetworkEvents(sock, socket_event.handle(), &net_events);
if (net_events.lNetworkEvents & FD_READ) {
if (consumeResultInput()) { if (consumeResultInput()) {
finished = true; finished = true;
} }
} }
else if (net_events.lNetworkEvents & FD_CLOSE) { else if (terminateRequested) {
doStateCallback({ ASyncDBConnection::State::NotConnected, "Close while waiting for result" });
finished = true;
stop();
}
}
if (res == wait_result_stop) {
// Send cancel, close connection and terminate thread // Send cancel, close connection and terminate thread
cancel(); cancel();
doStateCallback(ASyncDBConnection::State::Terminating); doStateCallback(ASyncDBConnection::State::Terminating);
@ -455,8 +461,8 @@ bool ASyncDBConnection::send(const std::string &command, on_result_callback on_r
{ {
std::lock_guard<std::mutex> lg(m_threadData->m_commandQueue.m_mutex); std::lock_guard<std::mutex> lg(m_threadData->m_commandQueue.m_mutex);
m_threadData->m_commandQueue.m_queue.emplace(command, on_result); m_threadData->m_commandQueue.m_queue.emplace(command, on_result);
m_threadData->m_commandQueue.m_newEvent.set();
} }
m_threadData->wake();
return true; return true;
} }
@ -468,8 +474,8 @@ bool ASyncDBConnection::send(const std::string &command, Pgsql::Params params, o
{ {
std::lock_guard<std::mutex> lg(m_threadData->m_commandQueue.m_mutex); std::lock_guard<std::mutex> lg(m_threadData->m_commandQueue.m_mutex);
m_threadData->m_commandQueue.m_queue.emplace(command, std::move(params), on_result); m_threadData->m_commandQueue.m_queue.emplace(command, std::move(params), on_result);
m_threadData->m_commandQueue.m_newEvent.set();
} }
m_threadData->wake();
return true; return true;
} }

View file

@ -11,6 +11,7 @@
#include <QElapsedTimer> #include <QElapsedTimer>
#include <mutex> #include <mutex>
#include <memory> #include <memory>
#include <thread>
class ASyncDBConnectionThread; class ASyncDBConnectionThread;
/** \brief Class that handles asynchronous execution of queries. /** \brief Class that handles asynchronous execution of queries.
@ -33,7 +34,7 @@ public:
class StateData { class StateData {
public: public:
State State; enum State State;
QString Message; QString Message;
StateData(enum State state, QString message = QString()) StateData(enum State state, QString message = QString())

189
pglablib/CMakeLists.txt Normal file
View file

@ -0,0 +1,189 @@
add_library(pglablib STATIC
Pglablib.cpp
ASyncDBConnection.cpp
ConnectionConfig.cpp
TestConnection.cpp
catalog/PgType.cpp
catalog/PgTypeContainer.cpp
catalog/PgNamespace.cpp
catalog/PgClass.cpp
catalog/PgDatabase.cpp
catalog/PgDatabaseContainer.cpp
catalog/PgAuthId.cpp
catalog/PgAuthIdContainer.cpp
catalog/PgClassContainer.cpp
catalog/PgDatabaseCatalog.cpp
catalog/PgNamespaceContainer.cpp
catalog/PgAttribute.cpp
catalog/PgContainer.cpp
catalog/PgAttributeContainer.cpp
catalog/PgIndex.cpp
catalog/PgIndexContainer.cpp
catalog/PgConstraint.cpp
catalog/PgConstraintContainer.cpp
ParamListJson.cpp
ParamListModel.cpp
sqlite/SQLiteConnection.cpp
sqlite/sqlite3.c
ui/catalog/tables/TableNode.cpp
ui/catalog/tables/TableSize.cpp
ui/catalog/tables/TableTreeBuilder.cpp
util.cpp
SqlFormattingUtils.cpp
catalog/PgKeywordList.cpp
QueryGenerator.cpp
ResultSourceMapping.cpp
catalog/PgObject.cpp
catalog/PgTablespace.cpp
catalog/PgTablespaceContainer.cpp
codebuilder/LanguageConfig.cpp
codebuilder/CodeBuilder.cpp
codebuilder/NameManglingRules.cpp
codebuilder/DefaultConfigs.cpp
codebuilder/TypeMappings.cpp
codebuilder/IndentationConfig.cpp
codebuilder/StructureTemplate.cpp
FormatToStream.cpp
codebuilder/StringLiteralRules.cpp
codebuilder/StringEscapeRule.cpp
catalog/PgTrigger.cpp
catalog/PgTriggerContainer.cpp
catalog/PgProc.cpp
catalog/PgProcContainer.cpp
catalog/PgDatabaseObject.cpp
catalog/PgServerObject.cpp
catalog/PgNamespaceObject.cpp
catalog/PgCollation.cpp
catalog/PgCollationContainer.cpp
catalog/PgInherits.cpp
catalog/PgInheritsContainer.cpp
SelectionEditorFactory.cpp
catalog/PgAm.cpp
model/TypeModelFactory.cpp
model/TypeSelectionItemModel.cpp
catalog/PgAmContainer.cpp
model/CollationModel.cpp
model/CollationModelFactory.cpp
catalog/PgLanguageContainer.cpp
catalog/PgLanguage.cpp
catalog/PgAcl.cpp
catalog/PgSequence.cpp
catalog/PgSequenceContainer.cpp
utils/HumanReadableBytes.cpp
utils/KeyStrengthener.cpp
utils/PasswordManager.cpp
utils/PostgresqlUrlParser.cpp
Pglablib.h
ASyncDBConnection.h
ConnectionConfig.h
TestConnection.h
catalog/PgType.h
catalog/PgTypeContainer.h
catalog/PgNamespace.h
catalog/PgClass.h
catalog/PgDatabase.h
catalog/PgDatabaseContainer.h
catalog/PgContainer.h
catalog/PgAuthId.h
catalog/PgAuthIdContainer.h
catalog/PgClassContainer.h
catalog/PgDatabaseCatalog.h
catalog/PgNamespaceContainer.h
catalog/PgAttribute.h
catalog/PgAttributeContainer.h
catalog/PgIndex.h
catalog/PgIndexContainer.h
catalog/PgConstraint.h
catalog/PgConstraintContainer.h
ParamListJson.h
ParamListModel.h
sqlite/SQLiteConnection.h
sqlite/sqlite3.h
sqlite/sqlite3ext.h
ui/catalog/tables/TableNode.h
ui/catalog/tables/TableSize.h
ui/catalog/tables/TableTreeBuilder.h
util.h
SqlFormattingUtils.h
catalog/PgCatalogTypes.h
catalog/PgKeywordList.h
QueryGenerator.h
ResultSourceMapping.h
catalog/PgObject.h
catalog/PgTablespace.h
catalog/PgTablespaceContainer.h
codebuilder/LanguageConfig.h
codebuilder/CodeBuilder.h
codebuilder/NameManglingRules.h
codebuilder/DefaultConfigs.h
codebuilder/TypeMappings.h
codebuilder/IndentationConfig.h
codebuilder/StructureTemplate.h
FormatToStream.h
codebuilder/ResultLoopTemplate.h
codebuilder/StringEscapeRule.h
codebuilder/StringLiteralRules.h
catalog/PgTrigger.h
catalog/PgTriggerContainer.h
catalog/PgProc.h
catalog/PgProcContainer.h
catalog/PgDatabaseObject.h
catalog/PgServerObject.h
catalog/PgNamespaceObject.h
catalog/PgCollation.h
catalog/PgCollationContainer.h
AbstractModelFactory.h
AbstractEditorFactory.h
catalog/PgInherits.h
catalog/PgInheritsContainer.h
SelectionEditorFactory.h
model/TypeSelectionItemModel.h
model/TypeModelFactory.h
catalog/PgAm.h
catalog/PgAmContainer.h
model/CollationModel.h
model/CollationModelFactory.h
catalog/PgLanguageContainer.h
catalog/PgLanguage.h
catalog/PgAcl.h
catalog/PgSequence.h
catalog/PgSequenceContainer.h
utils/HumanReadableBytes.h
utils/KeyStrengthener.h
utils/PasswordManager.h
utils/PostgresqlUrlParser.h
)
find_package(Boost REQUIRED COMPONENTS container timer chrono assert)
find_path(BOTAN_INCLUDE_DIR botan/hash.h REQUIRED)
find_library(BOTAN_LIBRARY NAMES botan-3 botan REQUIRED)
if(NOT TARGET Botan::Botan)
add_library(Botan::Botan STATIC IMPORTED)
set_target_properties(Botan::Botan PROPERTIES
IMPORTED_LOCATION "${BOTAN_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${BOTAN_INCLUDE_DIR}"
)
endif()
target_link_libraries(pglablib PUBLIC
core
pgsql
Boost::container
Boost::timer
Boost::chrono
Boost::assert
Botan::Botan
Qt6::Core
Qt6::Widgets
)
target_include_directories(pglablib PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
)
set_target_properties(pglablib PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
POSITION_INDEPENDENT_CODE ON
)

View file

@ -0,0 +1,159 @@
#include "ResultSourceMapping.h"
#include "Pgsql_Result.h"
#include "catalog/PgClass.h"
#include "catalog/PgDatabaseCatalog.h"
#include "catalog/PgConstraintContainer.h"
#include "catalog/PgNamespace.h"
#include "catalog/PgNamespaceContainer.h"
#include "catalog/PgClassContainer.h"
#include <algorithm>
#include <stdexcept>
namespace {
/// Whether a non-key column of the table is selected more than once.
/// A table that appears twice in a join (through aliases) shows up like that, and libpq can not tell the instances apart.
bool hasAmbiguousColumn(const std::vector<ResultColumnSource> &columns, Oid tableOid, const std::vector<int> &pkAttnums)
{
for (const auto &col : columns) {
if (col.tableOid != tableOid || std::find(pkAttnums.begin(), pkAttnums.end(), col.attnum) != pkAttnums.end())
continue;
auto count = std::count_if(columns.begin(), columns.end(), [&](const ResultColumnSource &other) {
return other.tableOid == tableOid && other.attnum == col.attnum;
});
if (count > 1)
return true;
}
return false;
}
}
ResultSourceMapping::ResultSourceMapping(std::vector<ResultColumnSource> columns, const std::vector<TablePrimaryKey> &primaryKeys)
: m_columns(std::move(columns))
{
for (const auto &col : m_columns) {
if (col.tableOid == InvalidOid || findTable(col.tableOid))
continue;
TableInfo info;
info.tableOid = col.tableOid;
auto pk = std::find_if(primaryKeys.begin(), primaryKeys.end(), [&](const TablePrimaryKey &key) {
return key.tableOid == col.tableOid;
});
if (pk != primaryKeys.end() && !pk->attnums.empty()) {
bool complete = true;
for (int attnum : pk->attnums) {
auto found = std::find_if(m_columns.begin(), m_columns.end(), [&](const ResultColumnSource &source) {
return source.tableOid == col.tableOid && source.attnum == attnum;
});
if (found == m_columns.end()) {
complete = false;
break;
}
info.pkColumns.push_back(static_cast<int>(std::distance(m_columns.begin(), found)));
}
info.editable = complete && !hasAmbiguousColumn(m_columns, col.tableOid, pk->attnums);
}
if (!info.editable)
info.pkColumns.clear();
m_tables.push_back(info);
}
}
int ResultSourceMapping::columnCount() const
{
return static_cast<int>(m_columns.size());
}
Oid ResultSourceMapping::tableOid(int col) const
{
if (col < 0 || col >= columnCount())
throw std::range_error("column index out of range");
return m_columns[col].tableOid;
}
int ResultSourceMapping::attnum(int col) const
{
if (col < 0 || col >= columnCount())
throw std::range_error("column index out of range");
return m_columns[col].attnum;
}
bool ResultSourceMapping::isEditable(int col) const
{
if (col < 0 || col >= columnCount())
throw std::range_error("column index out of range");
const auto &source = m_columns[col];
if (source.attnum <= 0)
return false;
const TableInfo *info = findTable(source.tableOid);
return info && info->editable;
}
std::vector<int> ResultSourceMapping::primaryKeyColumns(Oid tableOid) const
{
const TableInfo *info = findTable(tableOid);
return info ? info->pkColumns : std::vector<int>{};
}
const ResultSourceMapping::TableInfo *ResultSourceMapping::findTable(Oid tableOid) const
{
auto found = std::find_if(m_tables.begin(), m_tables.end(), [tableOid](const TableInfo &info) {
return info.tableOid == tableOid;
});
return found == m_tables.end() ? nullptr : &*found;
}
namespace {
/// Catalog tables (pg_catalog, information_schema, ...) are views or system tables and must never be edited.
bool isSystemCatalogTable(const PgDatabaseCatalog &catalog, Oid tableOid)
{
const PgClass *cls = catalog.classes()->getByKey(tableOid);
if (!cls)
return false;
const PgNamespace *ns = catalog.namespaces()->getByKey(cls->nsOid());
return ns && ns->isSystemCatalog();
}
}
ResultSourceMapping createResultSourceMapping(const Pgsql::Result &result, const PgDatabaseCatalog &catalog)
{
std::vector<ResultColumnSource> columns;
std::vector<TablePrimaryKey> primaryKeys;
for (int col = 0; col < result.cols(); ++col) {
ResultColumnSource source;
source.tableOid = result.ftable(col);
source.attnum = result.ftableCol(col);
columns.push_back(source);
if (source.tableOid == InvalidOid)
continue;
bool known = std::any_of(primaryKeys.begin(), primaryKeys.end(), [&](const TablePrimaryKey &key) {
return key.tableOid == source.tableOid;
});
if (known)
continue;
TablePrimaryKey pk;
pk.tableOid = source.tableOid;
if (!isSystemCatalogTable(catalog, source.tableOid)) {
auto constraint = catalog.constraints()->getPrimaryForRelation(source.tableOid);
if (constraint)
pk.attnums.assign(constraint->key.begin(), constraint->key.end());
}
primaryKeys.push_back(pk);
}
return ResultSourceMapping(std::move(columns), primaryKeys);
}

View file

@ -0,0 +1,61 @@
#ifndef RESULTSOURCEMAPPING_H
#define RESULTSOURCEMAPPING_H
#include <libpq-fe.h>
#include <vector>
class PgDatabaseCatalog;
namespace Pgsql {
class Result;
}
/// The table column a result column is read from, as reported by libpq (PQftable/PQftablecol).
/// tableOid is InvalidOid and attnum is 0 for expression columns.
struct ResultColumnSource {
Oid tableOid = InvalidOid;
int attnum = 0;
};
/// The primary key of a table, attnums in key order.
struct TablePrimaryKey {
Oid tableOid = InvalidOid;
std::vector<int> attnums;
};
/// Maps the columns of a query result to the table columns they come from and decides which
/// cells can be edited. A cell is editable when it maps to a user column of a table whose full
/// primary key is present in the result and which is not ambiguous.
class ResultSourceMapping {
public:
ResultSourceMapping(std::vector<ResultColumnSource> columns, const std::vector<TablePrimaryKey> &primaryKeys);
int columnCount() const;
/// Table the column is read from, InvalidOid for expression columns.
Oid tableOid(int col) const;
/// attnum of the column in its table, 0 for expression columns.
int attnum(int col) const;
bool isEditable(int col) const;
/// Result column indices holding the primary key of the table, in key order.
/// Empty when the table is not editable.
std::vector<int> primaryKeyColumns(Oid tableOid) const;
private:
struct TableInfo {
Oid tableOid = InvalidOid;
bool editable = false;
std::vector<int> pkColumns;
};
std::vector<ResultColumnSource> m_columns;
std::vector<TableInfo> m_tables;
const TableInfo *findTable(Oid tableOid) const;
};
/// Builds the mapping for a query result, looking up the primary keys of the source tables in the catalog.
ResultSourceMapping createResultSourceMapping(const Pgsql::Result &result, const PgDatabaseCatalog &catalog);
#endif // RESULTSOURCEMAPPING_H

View file

@ -200,7 +200,7 @@ QString quoteIdent(QString ident)
{ {
assert(ident.length() > 0); assert(ident.length() > 0);
static const wchar_t dquote = L'"'; static const QChar dquote = u'"';
if (identNeedsQuotes(ident)) { if (identNeedsQuotes(ident)) {
QString out; QString out;

View file

@ -1,31 +0,0 @@
#include "WaitHandleList.h"
#include "win32event.h"
WaitHandleList::WaitHandleList() = default;
WaitHandleList::~WaitHandleList() = default;
WaitResultValue WaitHandleList::add(HANDLE h)
{
m_waitHandles.push_back(h);
return WAIT_OBJECT_0 + static_cast<DWORD>(m_waitHandles.size() - 1);
}
WaitResultValue WaitHandleList::add(Win32Event &e)
{
return add(e.handle());
}
DWORD WaitHandleList::count() const
{
return static_cast<DWORD>(m_waitHandles.size());
}
void WaitHandleList::clear()
{
m_waitHandles.clear();
}
WaitHandleList::operator const HANDLE*() const
{
return m_waitHandles.data();
}

View file

@ -1,26 +0,0 @@
#ifndef WAITHANDLELIST_H
#define WAITHANDLELIST_H
#include <windows.h>
#include <vector>
class Win32Event;
using WaitResultValue = DWORD;
class WaitHandleList {
public:
WaitHandleList();
~WaitHandleList();
WaitResultValue add(HANDLE h);
WaitResultValue add(Win32Event &e);
DWORD count() const;
void clear();
operator const HANDLE*() const;
private:
std::vector<HANDLE> m_waitHandles;
};
#endif // WAITHANDLELIST_H

View file

@ -1,47 +0,0 @@
#ifndef WIN32EVENT_H
#define WIN32EVENT_H
#include <WinSock2.h>
#include <windows.h>
/** Simpel wrapper around a Win32 Event object.
Mostly to make cleanup automatic.*/
class Win32Event {
public:
enum class Reset { Auto=0, Manual=1 };
enum class Initial { Clear=0, Set=1 };
Win32Event(Reset r, Initial is)
: hEvent(CreateEvent(
nullptr, // _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL(r), // _In_ BOOL bManualReset,
BOOL(is), // _In_ BOOL bInitialState,
nullptr //_In_opt_ LPCTSTR lpName
))
{}
Win32Event(Reset r, Initial is, int sock, long net_events)
: Win32Event(r, is)
{
WSAEventSelect(sock, hEvent, net_events);
}
~Win32Event()
{
CloseHandle(hEvent);
}
Win32Event(const Win32Event &) = delete;
Win32Event &operator=(const Win32Event &) = delete;
void set() { SetEvent(hEvent); }
void reset() { ResetEvent(hEvent); }
HANDLE handle() { return hEvent; }
private:
HANDLE hEvent;
};
#endif // WIN32EVENT_H

View file

@ -1,13 +1,15 @@
#include "PgLanguage.h" #include "PgLanguage.h"
#include <stdexcept>
QString PgLanguage::createSql() const QString PgLanguage::createSql() const
{ {
throw std::exception("PgLanguage::createSql() not implemented"); throw std::logic_error("PgLanguage::createSql() not implemented");
} }
QString PgLanguage::dropSql() const QString PgLanguage::dropSql() const
{ {
throw std::exception("PgLanguage::dropSql() not implemented"); throw std::logic_error("PgLanguage::dropSql() not implemented");
} }
QString PgLanguage::typeName() const QString PgLanguage::typeName() const

View file

@ -48,15 +48,15 @@ namespace {
for(; index < len; index++) { for(; index < len; index++) {
QChar ch = str[index]; QChar ch = str[index];
if (!double_quote && ch == L'\'') if (!double_quote && ch == u'\'')
single_quote = !single_quote; single_quote = !single_quote;
else if (!single_quote && ch == L'"') else if (!single_quote && ch == u'"')
double_quote = !double_quote; double_quote = !double_quote;
else if (!double_quote && !single_quote && ch == L'(') else if (!double_quote && !single_quote && ch == u'(')
brackets++; brackets++;
else if (!double_quote && !single_quote && ch == L')') else if (!double_quote && !single_quote && ch == u')')
brackets--; brackets--;
else if (!double_quote && !single_quote && brackets == 0 && ch == L',') { else if (!double_quote && !single_quote && brackets == 0 && ch == u',') {
if (index != start_array) if (index != start_array)
res.push_back(str.mid(start_array, index - 1).trimmed()); res.push_back(str.mid(start_array, index - 1).trimmed());
else else

View file

@ -38,7 +38,7 @@ QString PgServerObject::alterOwnerSql(const QString& ident) const
} }
void PgServerObject::setAcls(boost::optional<AclList> acls) void PgServerObject::setAcls(std::optional<AclList> acls)
{ {
m_acls = std::move(acls); m_acls = std::move(acls);
} }

View file

@ -4,7 +4,7 @@
#include <QString> #include <QString>
#include "PgObject.h" #include "PgObject.h"
#include "PgAcl.h" #include "PgAcl.h"
#include <boost/optional.hpp> #include <optional>
class PgAuthId; class PgAuthId;
@ -26,7 +26,7 @@ public:
* @brief setAcls * @brief setAcls
* @param acls Important: pass empty optional when acl IS NULL, pass empty list for empty array * @param acls Important: pass empty optional when acl IS NULL, pass empty list for empty array
*/ */
void setAcls(boost::optional<AclList> acls); void setAcls(std::optional<AclList> acls);
QString aclString() const; QString aclString() const;
/** /**
* @brief grantSql * @brief grantSql
@ -53,7 +53,7 @@ protected:
private: private:
Oid m_ownerOid = InvalidOid; Oid m_ownerOid = InvalidOid;
const PgAuthId * m_owner = nullptr; const PgAuthId * m_owner = nullptr;
boost::optional<AclList> m_acls; std::optional<AclList> m_acls;
}; };
#endif // PGSERVEROBJECT_H #endif // PGSERVEROBJECT_H

View file

@ -2,7 +2,6 @@
#define STRINGESCAPERULE_H #define STRINGESCAPERULE_H
#include <QString> #include <QString>
#include <boost/optional.hpp>
enum class ConvertToNumericEscape { enum class ConvertToNumericEscape {
Utf8, ///< Generates upto four escapes for 4 utf8 chars Utf8, ///< Generates upto four escapes for 4 utf8 chars

View file

@ -1,222 +0,0 @@
#-------------------------------------------------
#
# Project created by QtCreator 2017-12-13T17:36:43
#
#-------------------------------------------------
QT += widgets core
TARGET = pglablib
TEMPLATE = lib
CONFIG += staticlib
! include( ../common.pri ) {
error( "Couldn't find the common.pri file!" )
}
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
Pglablib.cpp \
ASyncDBConnection.cpp \
ConnectionConfig.cpp \
TestConnection.cpp \
WaitHandleList.cpp \
catalog/PgType.cpp \
catalog/PgTypeContainer.cpp \
catalog/PgNamespace.cpp \
catalog/PgClass.cpp \
catalog/PgDatabase.cpp \
catalog/PgDatabaseContainer.cpp \
catalog/PgAuthId.cpp \
catalog/PgAuthIdContainer.cpp \
catalog/PgClassContainer.cpp \
catalog/PgDatabaseCatalog.cpp \
catalog/PgNamespaceContainer.cpp \
catalog/PgAttribute.cpp \
catalog/PgContainer.cpp \
catalog/PgAttributeContainer.cpp \
catalog/PgIndex.cpp \
catalog/PgIndexContainer.cpp \
catalog/PgConstraint.cpp \
catalog/PgConstraintContainer.cpp \
ParamListJson.cpp \
ParamListModel.cpp \
sqlite/SQLiteConnection.cpp \
sqlite/sqlite3.c \
ui/catalog/tables/TableNode.cpp \
ui/catalog/tables/TableSize.cpp \
ui/catalog/tables/TableTreeBuilder.cpp \
util.cpp \
SqlFormattingUtils.cpp \
catalog/PgKeywordList.cpp \
QueryGenerator.cpp \
catalog/PgObject.cpp \
catalog/PgTablespace.cpp \
catalog/PgTablespaceContainer.cpp \
codebuilder/LanguageConfig.cpp \
codebuilder/CodeBuilder.cpp \
codebuilder/NameManglingRules.cpp \
codebuilder/DefaultConfigs.cpp \
codebuilder/TypeMappings.cpp \
codebuilder/IndentationConfig.cpp \
codebuilder/StructureTemplate.cpp \
FormatToStream.cpp \
codebuilder/StringLiteralRules.cpp \
codebuilder/StringEscapeRule.cpp \
catalog/PgTrigger.cpp \
catalog/PgTriggerContainer.cpp \
catalog/PgProc.cpp \
catalog/PgProcContainer.cpp \
catalog/PgDatabaseObject.cpp \
catalog/PgServerObject.cpp \
catalog/PgNamespaceObject.cpp \
catalog/PgCollation.cpp \
catalog/PgCollationContainer.cpp \
catalog/PgInherits.cpp \
catalog/PgInheritsContainer.cpp \
SelectionEditorFactory.cpp \
catalog/PgAm.cpp \
model/TypeModelFactory.cpp \
model/TypeSelectionItemModel.cpp \
catalog/PgAmContainer.cpp \
model/CollationModel.cpp \
model/CollationModelFactory.cpp \
catalog/PgLanguageContainer.cpp \
catalog/PgLanguage.cpp \
catalog/PgAcl.cpp \
catalog/PgSequence.cpp \
catalog/PgSequenceContainer.cpp \
utils/HumanReadableBytes.cpp \
utils/KeyStrengthener.cpp \
utils/PasswordManager.cpp \
utils/PostgresqlUrlParser.cpp
HEADERS += \
Pglablib.h \
ASyncDBConnection.h \
ConnectionConfig.h \
TestConnection.h \
WaitHandleList.h \
Win32Event.h \
catalog/PgType.h \
catalog/PgTypeContainer.h \
catalog/PgNamespace.h \
catalog/PgClass.h \
catalog/PgDatabase.h \
catalog/PgDatabaseContainer.h \
catalog/PgContainer.h \
catalog/PgAuthId.h \
catalog/PgAuthIdContainer.h \
catalog/PgClassContainer.h \
catalog/PgDatabaseCatalog.h \
catalog/PgNamespaceContainer.h \
catalog/PgAttribute.h \
catalog/PgAttributeContainer.h \
catalog/PgIndex.h \
catalog/PgIndexContainer.h \
catalog/PgConstraint.h \
catalog/PgConstraintContainer.h \
ParamListJson.h \
ParamListModel.h \
sqlite/SQLiteConnection.h \
sqlite/sqlite3.h \
sqlite/sqlite3ext.h \
ui/catalog/tables/TableNode.h \
ui/catalog/tables/TableSize.h \
ui/catalog/tables/TableTreeBuilder.h \
util.h \
SqlFormattingUtils.h \
catalog/PgCatalogTypes.h \
catalog/PgKeywordList.h \
QueryGenerator.h \
catalog/PgObject.h \
catalog/PgTablespace.h \
catalog/PgTablespaceContainer.h \
codebuilder/LanguageConfig.h \
codebuilder/CodeBuilder.h \
codebuilder/NameManglingRules.h \
codebuilder/DefaultConfigs.h \
codebuilder/TypeMappings.h \
codebuilder/IndentationConfig.h \
codebuilder/StructureTemplate.h \
FormatToStream.h \
codebuilder/ResultLoopTemplate.h \
codebuilder/StringEscapeRule.h \
codebuilder/StringLiteralRules.h \
catalog/PgTrigger.h \
catalog/PgTriggerContainer.h \
catalog/PgProc.h \
catalog/PgProcContainer.h \
catalog/PgDatabaseObject.h \
catalog/PgServerObject.h \
catalog/PgNamespaceObject.h \
catalog/PgCollation.h \
catalog/PgCollationContainer.h \
AbstractModelFactory.h \
AbstractEditorFactory.h \
catalog/PgInherits.h \
catalog/PgInheritsContainer.h \
SelectionEditorFactory.h \
model/TypeSelectionItemModel.h \
model/TypeModelFactory.h \
catalog/PgAm.h \
catalog/PgAmContainer.h \
model/CollationModel.h \
model/CollationModelFactory.h \
catalog/PgLanguageContainer.h \
catalog/PgLanguage.h \
catalog/PgAcl.h \
catalog/PgSequence.h \
catalog/PgSequenceContainer.h \
utils/HumanReadableBytes.h \
utils/KeyStrengthener.h \
utils/PasswordManager.h \
utils/PostgresqlUrlParser.h
unix {
target.path = /usr/lib
INSTALLS += target
}
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../core/release/ -lcore
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../core/debug/ -lcore
else:unix:!macx: LIBS += -L$$OUT_PWD/../core/ -lcore
INCLUDEPATH += $$PWD/../core
DEPENDPATH += $$PWD/../core
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/libcore.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/libcore.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/core.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/core.lib
else:unix:!macx: PRE_TARGETDEPS += $$OUT_PWD/../core/libcore.a
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../pgsql/release/ -lpgsql
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../pgsql/debug/ -lpgsql
else:unix:!macx: LIBS += -L$$OUT_PWD/../pgsql/ -lpgsql
INCLUDEPATH += $$PWD/../pgsql
DEPENDPATH += $$PWD/../pgsql
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pgsql/release/libpgsql.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pgsql/debug/libpgsql.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pgsql/release/pgsql.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../pgsql/debug/pgsql.lib
else:unix:!macx: PRE_TARGETDEPS += $$OUT_PWD/../pgsql/libpgsql.a
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../core/release/ -lcore
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../core/debug/ -lcore
else:unix: LIBS += -L$$OUT_PWD/../core/ -lcore
INCLUDEPATH += $$PWD/../core
DEPENDPATH += $$PWD/../core
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/libcore.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/libcore.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/core.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/core.lib
else:unix: PRE_TARGETDEPS += $$OUT_PWD/../core/libcore.a

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <cstdint>
#include <string> #include <string>
std::string HumanReadableBytes(uint64_t bytes); std::string HumanReadableBytes(uint64_t bytes);

View file

@ -9,7 +9,7 @@
#include <botan/base64.h> #include <botan/base64.h>
#include <botan/mac.h> #include <botan/mac.h>
#include <botan/block_cipher.h> #include <botan/block_cipher.h>
#include <boost/lexical_cast.hpp> #include <botan/exceptn.h>
using namespace Botan; using namespace Botan;
@ -33,7 +33,7 @@ public:
* distinct key which is derived by hashing the wrapped key name with HMAC. * distinct key which is derived by hashing the wrapped key name with HMAC.
*/ */
const std::vector<uint8_t> wrapped_name = const std::vector<uint8_t> wrapped_name =
nist_key_wrap_padded(cast_char_ptr_to_uint8(name.data()), nist_key_wrap_padded(reinterpret_cast<const uint8_t*>(name.data()),
name.size(), name.size(),
*m_cipher); *m_cipher);
@ -47,7 +47,7 @@ public:
secure_vector<uint8_t> get(const std::string& name, const std::string_view &wrapped_key) const secure_vector<uint8_t> get(const std::string& name, const std::string_view &wrapped_key) const
{ {
const std::vector<uint8_t> wrapped_name = const std::vector<uint8_t> wrapped_name =
nist_key_wrap_padded(cast_char_ptr_to_uint8(name.data()), nist_key_wrap_padded(reinterpret_cast<const uint8_t*>(name.data()),
name.size(), name.size(),
*m_cipher); *m_cipher);

View file

@ -19,7 +19,11 @@ namespace Botan {
class PasswordManagerException: public std::exception { class PasswordManagerException: public std::exception {
public: public:
using std::exception::exception; //(char const* const _Message); PasswordManagerException() = default;
explicit PasswordManagerException(const char *message) : m_message(message) {}
const char *what() const noexcept override { return m_message.c_str(); }
private:
std::string m_message;
}; };
class PasswordManagerLockedException: public PasswordManagerException { class PasswordManagerLockedException: public PasswordManagerException {

View file

@ -1,4 +1,5 @@
#include "ArrayParser.h" #include "ArrayParser.h"
#include <cstring>
#include <stdexcept> #include <stdexcept>
using namespace Pgsql; using namespace Pgsql;

48
pgsql/CMakeLists.txt Normal file
View file

@ -0,0 +1,48 @@
find_package(PostgreSQL REQUIRED)
add_library(pgsql STATIC
Pgsql_Connection.cpp
Pgsql_Params.cpp
Pgsql_PgException.cpp
Pgsql_Result.cpp
Pgsql_Row.cpp
Pgsql_Value.cpp
ArrayParser.cpp
Pgsql_oids.cpp
Pgsql_Transaction.cpp
Pgsql_ErrorDetails.cpp
Pgsql_Canceller.cpp
Pgsql_Connection.h
Pgsql_IResult.h
Pgsql_Params.h
Pgsql_PgException.h
Pgsql_Result.h
Pgsql_ResultConstIterator.h
Pgsql_Row.h
Pgsql_Value.h
Pgsql_declare.h
Pgsql_Col.h
ArrayParser.h
Pgsql_oids.h
SqlGenerator.h
Pgsql_Transaction.h
Pgsql_ErrorDetails.h
Pgsql_Canceller.h
)
target_link_libraries(pgsql PUBLIC
core
PostgreSQL::PostgreSQL
Qt6::Core
Qt6::Widgets
)
target_include_directories(pgsql PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
)
set_target_properties(pgsql PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
POSITION_INDEPENDENT_CODE ON
)

View file

@ -1,60 +0,0 @@
#-------------------------------------------------
#
# Project created by QtCreator 2016-11-11T09:17:13
#
#-------------------------------------------------
CONFIG += staticlib
QT += core
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = pgsql
TEMPLATE = lib
! include( ../common.pri ) {
error( "Couldn't find the common.pri file!" )
}
LIBS += -lws2_32 -llibpq
SOURCES += Pgsql_Connection.cpp \
Pgsql_Params.cpp \
Pgsql_PgException.cpp \
Pgsql_Result.cpp \
Pgsql_Row.cpp \
Pgsql_Value.cpp \
ArrayParser.cpp \
Pgsql_oids.cpp \
Pgsql_Transaction.cpp \
Pgsql_ErrorDetails.cpp \
Pgsql_Canceller.cpp
HEADERS += Pgsql_Connection.h \
Pgsql_IResult.h \
Pgsql_Params.h \
Pgsql_PgException.h \
Pgsql_Result.h \
Pgsql_ResultConstIterator.h \
Pgsql_Row.h \
Pgsql_Value.h \
Pgsql_declare.h \
Pgsql_Col.h \
ArrayParser.h \
Pgsql_oids.h \
SqlGenerator.h \
Pgsql_Transaction.h \
Pgsql_ErrorDetails.h \
Pgsql_Canceller.h
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../core/release/ -lcore
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../core/debug/ -lcore
INCLUDEPATH += $$PWD/../core
DEPENDPATH += $$PWD/../core
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/libcore.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/libcore.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/core.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/core.lib

View file

@ -0,0 +1,13 @@
---
features:
- |
Data grids in the table editor and in query results now have a toolbar
with :menuselection:`Save` and :menuselection:`Revert` buttons, Save can
also be triggered with :kbd:`Ctrl+S`. Both buttons are only enabled while
there are unsaved changes.
other:
- |
Edits in data grids are no longer saved automatically when a cell editor
is closed or when :kbd:`Escape` is pressed. Use the :menuselection:`Save`
button to write changes to the database, or :menuselection:`Revert` to
discard them. Refreshing a table with unsaved changes asks for confirmation.

View file

@ -0,0 +1,13 @@
---
features:
- |
Query results are now shown in the same grid as the table data tab. Columns
can be sorted by clicking their headers, and cells that belong to a table
with a primary key present in the result can be edited directly. Changes
are saved with an ``UPDATE`` by primary key. Tables in system catalog
schemas such as ``pg_catalog`` and ``information_schema`` are never editable.
other:
- |
Read-only cells in query results, such as expression columns, columns of
tables without a primary key in the result, or columns of system catalog
tables, are now shown in grey so they can be told apart from editable cells.

5
tests/CMakeLists.txt Normal file
View file

@ -0,0 +1,5 @@
find_package(GTest CONFIG REQUIRED)
add_subdirectory(mygtestutils)
add_subdirectory(PgsqlTests)
add_subdirectory(pglabtests)

View file

@ -0,0 +1,24 @@
add_executable(PgsqlTests
main.cpp
tst_Value.cpp
tst_ArrayParser.cpp
tst_Pgsql_oids.cpp
)
target_link_libraries(PgsqlTests PRIVATE
pgsql
mygtestutils
GTest::gtest
Qt6::Core
)
target_include_directories(PgsqlTests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
set_target_properties(PgsqlTests PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
)
add_test(NAME PgsqlTests COMMAND PgsqlTests)

View file

@ -1,46 +0,0 @@
! include( ../../common.pri ) {
error( "Couldn't find the common.pri file!" )
}
include(gtest_dependency.pri)
TEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG += thread
CONFIG += qt
QT += core
HEADERS +=
SOURCES += main.cpp \
tst_Value.cpp \
tst_ArrayParser.cpp \
tst_Pgsql_oids.cpp
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../pgsql/release/ -lpgsql
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../pgsql/debug/ -lpgsql
else:unix: LIBS += -L$$OUT_PWD/../../pgsql/ -lpgsql
INCLUDEPATH += $$PWD/../../pgsql
DEPENDPATH += $$PWD/../../pgsql
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/release/libpgsql.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/debug/libpgsql.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/release/pgsql.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/debug/pgsql.lib
else:unix: PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/libpgsql.a
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../mygtestutils/release/ -lmygtestutils
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../mygtestutils/debug/ -lmygtestutils
else:unix:!macx: LIBS += -L$$OUT_PWD/../mygtestutils/ -lmygtestutils
INCLUDEPATH += $$PWD/../mygtestutils
DEPENDPATH += $$PWD/../mygtestutils
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/release/libmygtestutils.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/debug/libmygtestutils.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/release/mygtestutils.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/debug/mygtestutils.lib
else:unix:!macx: PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/libmygtestutils.a

View file

@ -1,29 +0,0 @@
isEmpty(GOOGLETEST_DIR):GOOGLETEST_DIR=$$(GOOGLETEST_DIR)
isEmpty(GOOGLETEST_DIR) {
warning("Using googletest src dir specified at Qt Creator wizard")
message("set GOOGLETEST_DIR as environment variable or qmake variable to get rid of this message")
GOOGLETEST_DIR = C:/Prog/googletest
}
!isEmpty(GOOGLETEST_DIR): {
GTEST_SRCDIR = $$GOOGLETEST_DIR/googletest
GMOCK_SRCDIR = $$GOOGLETEST_DIR/googlemock
}
requires(exists($$GTEST_SRCDIR):exists($$GMOCK_SRCDIR))
!exists($$GOOGLETEST_DIR):message("No googletest src dir found - set GOOGLETEST_DIR to enable.")
DEFINES += \
GTEST_LANG_CXX11
INCLUDEPATH *= \
$$GTEST_SRCDIR \
$$GTEST_SRCDIR/include \
$$GMOCK_SRCDIR \
$$GMOCK_SRCDIR/include
SOURCES += \
$$GTEST_SRCDIR/src/gtest-all.cc \
$$GMOCK_SRCDIR/src/gmock-all.cc

View file

@ -0,0 +1,19 @@
add_library(mygtestutils STATIC
PrintTo_Qt.cpp
PrintTo_Qt.h
)
target_link_libraries(mygtestutils PUBLIC
GTest::gtest
Qt6::Core
)
target_include_directories(mygtestutils PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
set_target_properties(mygtestutils PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
POSITION_INDEPENDENT_CODE ON
)

View file

@ -1,32 +0,0 @@
#-------------------------------------------------
#
# Project created by QtCreator 2017-12-01T10:07:31
#
#-------------------------------------------------
QT -= gui
TARGET = mygtestutils
TEMPLATE = lib
CONFIG += staticlib
# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
PrintTo_Qt.cpp
HEADERS += \
PrintTo_Qt.h
unix {
target.path = /usr/lib
INSTALLS += target
}

View file

@ -0,0 +1,42 @@
add_executable(pglabtests
main.cpp
TableTreeBuilderTests.cpp
tst_ConvertLangToSqlString.cpp
tst_ConvertToMultiLineCString.cpp
tst_ExplainJsonParser.cpp
tst_HumanReadableBytes.cpp
tst_TryParsePostgresqlUrl.cpp
tst_escapeConnectionStringValue.cpp
tst_expected.cpp
tst_SqlLexer.cpp
tst_scopeguard.cpp
tst_CsvWriter.cpp
tst_PasswordManager.cpp
tst_ParamJson.cpp
tst_CodeBuilder.cpp
tst_NameManglingRules.cpp
tst_TypeMappings.cpp
tst_Range.cpp
ResultSourceMappingTest.cpp
)
target_link_libraries(pglabtests PRIVATE
pglablib
pgsql
core
mygtestutils
GTest::gtest
Qt6::Core
Qt6::Widgets
)
target_include_directories(pglabtests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
set_target_properties(pglabtests PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
)
add_test(NAME pglabtests COMMAND pglabtests)

View file

@ -0,0 +1,168 @@
#include <gtest/gtest.h>
#include <gmock/gmock-matchers.h>
#include "ResultSourceMapping.h"
namespace {
constexpr Oid tableA = 1001;
constexpr Oid tableB = 1002;
ResultColumnSource src(Oid table, int attnum)
{
ResultColumnSource s;
s.tableOid = table;
s.attnum = attnum;
return s;
}
ResultColumnSource expression()
{
return ResultColumnSource{};
}
TablePrimaryKey pk(Oid table, std::vector<int> attnums)
{
TablePrimaryKey key;
key.tableOid = table;
key.attnums = std::move(attnums);
return key;
}
}
TEST(ResultSourceMappingTest, single_table_with_pk_all_columns_editable)
{
ResultSourceMapping m({src(tableA, 1), src(tableA, 2), src(tableA, 3)}, {pk(tableA, {1})});
EXPECT_EQ(m.columnCount(), 3);
EXPECT_TRUE(m.isEditable(0));
EXPECT_TRUE(m.isEditable(1));
EXPECT_TRUE(m.isEditable(2));
EXPECT_EQ(m.tableOid(1), tableA);
EXPECT_EQ(m.attnum(2), 3);
EXPECT_THAT(m.primaryKeyColumns(tableA), ::testing::ElementsAre(0));
}
TEST(ResultSourceMappingTest, composite_pk_maps_key_columns_in_key_order)
{
ResultSourceMapping m({src(tableA, 3), src(tableA, 1), src(tableA, 2)}, {pk(tableA, {1, 2})});
EXPECT_TRUE(m.isEditable(0));
EXPECT_THAT(m.primaryKeyColumns(tableA), ::testing::ElementsAre(1, 2));
}
TEST(ResultSourceMappingTest, join_with_both_pks_both_tables_editable)
{
ResultSourceMapping m({src(tableA, 1), src(tableA, 2), src(tableB, 1), src(tableB, 2)},
{pk(tableA, {1}), pk(tableB, {1})});
for (int col = 0; col < m.columnCount(); ++col) {
EXPECT_TRUE(m.isEditable(col)) << "column " << col;
}
EXPECT_THAT(m.primaryKeyColumns(tableA), ::testing::ElementsAre(0));
EXPECT_THAT(m.primaryKeyColumns(tableB), ::testing::ElementsAre(2));
}
TEST(ResultSourceMappingTest, join_where_only_one_table_has_pk_in_result)
{
ResultSourceMapping m({src(tableA, 1), src(tableA, 2), src(tableB, 2)},
{pk(tableA, {1}), pk(tableB, {1})});
EXPECT_TRUE(m.isEditable(0));
EXPECT_TRUE(m.isEditable(1));
EXPECT_FALSE(m.isEditable(2));
EXPECT_TRUE(m.primaryKeyColumns(tableB).empty());
}
TEST(ResultSourceMappingTest, pk_missing_from_result_is_read_only)
{
ResultSourceMapping m({src(tableA, 2), src(tableA, 3)}, {pk(tableA, {1})});
EXPECT_FALSE(m.isEditable(0));
EXPECT_FALSE(m.isEditable(1));
EXPECT_TRUE(m.primaryKeyColumns(tableA).empty());
}
TEST(ResultSourceMappingTest, composite_pk_partially_present_is_read_only)
{
ResultSourceMapping m({src(tableA, 1), src(tableA, 3)}, {pk(tableA, {1, 2})});
EXPECT_FALSE(m.isEditable(0));
EXPECT_FALSE(m.isEditable(1));
}
TEST(ResultSourceMappingTest, table_without_pk_is_read_only)
{
ResultSourceMapping m({src(tableA, 1), src(tableA, 2)}, {});
EXPECT_FALSE(m.isEditable(0));
EXPECT_FALSE(m.isEditable(1));
}
TEST(ResultSourceMappingTest, expression_column_is_read_only)
{
ResultSourceMapping m({src(tableA, 1), expression(), src(tableA, 2)}, {pk(tableA, {1})});
EXPECT_TRUE(m.isEditable(0));
EXPECT_FALSE(m.isEditable(1));
EXPECT_EQ(m.tableOid(1), InvalidOid);
EXPECT_EQ(m.attnum(1), 0);
EXPECT_TRUE(m.isEditable(2));
}
TEST(ResultSourceMappingTest, system_column_is_read_only)
{
ResultSourceMapping m({src(tableA, 1), src(tableA, -2)}, {pk(tableA, {1})});
EXPECT_TRUE(m.isEditable(0));
EXPECT_FALSE(m.isEditable(1));
}
TEST(ResultSourceMappingTest, duplicate_pk_column_still_editable)
{
ResultSourceMapping m({src(tableA, 1), src(tableA, 1), src(tableA, 2)}, {pk(tableA, {1})});
EXPECT_TRUE(m.isEditable(0));
EXPECT_TRUE(m.isEditable(1));
EXPECT_TRUE(m.isEditable(2));
EXPECT_THAT(m.primaryKeyColumns(tableA), ::testing::ElementsAre(0));
}
TEST(ResultSourceMappingTest, same_non_pk_column_twice_makes_table_read_only)
{
ResultSourceMapping m({src(tableA, 1), src(tableA, 2), src(tableA, 2)}, {pk(tableA, {1})});
EXPECT_FALSE(m.isEditable(0));
EXPECT_FALSE(m.isEditable(1));
EXPECT_FALSE(m.isEditable(2));
EXPECT_TRUE(m.primaryKeyColumns(tableA).empty());
}
TEST(ResultSourceMappingTest, self_join_with_aliased_non_pk_columns_is_read_only)
{
ResultSourceMapping m({src(tableA, 1), src(tableA, 1), src(tableA, 2), src(tableA, 2)}, {pk(tableA, {1})});
EXPECT_FALSE(m.isEditable(0));
EXPECT_FALSE(m.isEditable(2));
}
TEST(ResultSourceMappingTest, out_of_range_column_throws)
{
ResultSourceMapping m({src(tableA, 1)}, {pk(tableA, {1})});
EXPECT_THROW(m.isEditable(1), std::range_error);
EXPECT_THROW(m.isEditable(-1), std::range_error);
EXPECT_THROW(m.tableOid(1), std::range_error);
EXPECT_THROW(m.attnum(-1), std::range_error);
}
TEST(ResultSourceMappingTest, empty_result_has_no_columns)
{
ResultSourceMapping m({}, {});
EXPECT_EQ(m.columnCount(), 0);
EXPECT_TRUE(m.primaryKeyColumns(tableA).empty());
}

View file

@ -1,86 +0,0 @@
! include( ../../common.pri ) {
error( "Couldn't find the common.pri file!" )
}
include(gtest_dependency.pri)
TEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG += thread
CONFIG += qt
QT += core widgets
HEADERS +=
SOURCES += main.cpp \
TableTreeBuilderTests.cpp \
tst_ConvertLangToSqlString.cpp \
tst_ConvertToMultiLineCString.cpp \
tst_ExplainJsonParser.cpp \
tst_HumanReadableBytes.cpp \
tst_TryParsePostgresqlUrl.cpp \
tst_escapeConnectionStringValue.cpp \
tst_expected.cpp \
tst_SqlLexer.cpp \
tst_scopeguard.cpp \
tst_CsvWriter.cpp \
tst_PasswordManager.cpp \
tst_ParamJson.cpp \
tst_CodeBuilder.cpp \
tst_NameManglingRules.cpp \
tst_TypeMappings.cpp \
tst_Range.cpp
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../core/release/ -lcore
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../core/debug/ -lcore
INCLUDEPATH += $$PWD/../../core
DEPENDPATH += $$PWD/../../core
win32:CONFIG(debug, debug|release): LIBS += -lbotand
else:win32:CONFIG(release, debug|release): LIBS += -lbotan
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../core/release/libcore.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../core/debug/libcore.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../core/release/core.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../core/debug/core.lib
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../pgsql/release/ -lpgsql
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../pgsql/debug/ -lpgsql
else:unix:!macx: LIBS += -L$$OUT_PWD/../../pgsql/ -lpgsql
INCLUDEPATH += $$PWD/../../pgsql
DEPENDPATH += $$PWD/../../pgsql
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/release/libpgsql.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/debug/libpgsql.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/release/pgsql.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/debug/pgsql.lib
else:unix:!macx: PRE_TARGETDEPS += $$OUT_PWD/../../pgsql/libpgsql.a
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../pglablib/release/ -lpglablib
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../pglablib/debug/ -lpglablib
else:unix:!macx: LIBS += -L$$OUT_PWD/../../pglablib/ -lpglablib
INCLUDEPATH += $$PWD/../../pglablib
DEPENDPATH += $$PWD/../../pglablib
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pglablib/release/libpglablib.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pglablib/debug/libpglablib.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pglablib/release/pglablib.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../pglablib/debug/pglablib.lib
else:unix:!macx: PRE_TARGETDEPS += $$OUT_PWD/../../pglablib/libpglablib.a
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../mygtestutils/release/ -lmygtestutils
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../mygtestutils/debug/ -lmygtestutils
else:unix:!macx: LIBS += -L$$OUT_PWD/../mygtestutils/ -lmygtestutils
INCLUDEPATH += $$PWD/../mygtestutils
DEPENDPATH += $$PWD/../mygtestutils
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/release/libmygtestutils.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/debug/libmygtestutils.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/release/mygtestutils.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/debug/mygtestutils.lib
else:unix:!macx: PRE_TARGETDEPS += $$OUT_PWD/../mygtestutils/libmygtestutils.a

View file

@ -1,5 +0,0 @@
TEMPLATE = subdirs
SUBDIRS += PgsqlTests \
pglabtests \
mygtestutils

13
vcpkg.json Normal file
View file

@ -0,0 +1,13 @@
{
"name": "pglab",
"version-string": "0.1.0",
"dependencies": [
"boost-container",
"boost-timer",
"boost-chrono",
"boost-assert",
"botan",
"libpq",
"gtest"
]
}