1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2024-12-12 10:04:14 +02:00

More efficient memory allocation for Strings and String Variants.

The vast majority of Strings are never modified so for most cases allocate memory for the string with the object. This results in one allocation in most cases instead of two. Use strNew() if strCat*() functions are needed.

Update varNewStr() in the same way since String Variants can never be modified. This results in one allocation in all cases instead of three. Also update varNewStrZ() to use STR() instead of strNewZ() to save two more allocations.
This commit is contained in:
David Steele 2021-10-07 19:43:28 -04:00
parent 208641ac7f
commit b7e17d80ea
39 changed files with 283 additions and 230 deletions

View File

@ -38,7 +38,8 @@ bldDefineRender(const String *const define, const String *const value)
__attribute__((always_inline)) static inline String *
bldHeader(const char *const module, const char *const description)
{
return strNewFmt(
return strCatFmt(
strNew(),
COMMENT_BLOCK_BEGIN "\n"
"%s\n"
"\n"

View File

@ -32,7 +32,7 @@ Build enum from a string
static String *
bldEnum(const char *const prefix, const String *const value)
{
String *const result = strNewZ(prefix);
String *const result = strCatZ(strNew(), prefix);
const char *const valuePtr = strZ(value);
bool upper = true;
@ -82,7 +82,8 @@ Render config.auto.h
static void
bldCfgRenderConfigAutoH(const Storage *const storageRepo, const BldCfg bldCfg)
{
String *config = strNewFmt(
String *config = strCatFmt(
strNew(),
"%s"
"#ifndef CONFIG_CONFIG_AUTO_H\n"
"#define CONFIG_CONFIG_AUTO_H\n",

View File

@ -48,7 +48,8 @@ Render error.auto.h
static void
bldErrRenderErrorAutoH(const Storage *const storageRepo, const BldErr bldErr)
{
String *error = strNewFmt(
String *error = strCatFmt(
strNew(),
"%s"
"#ifndef COMMON_ERROR_AUTO_H\n"
"#define COMMON_ERROR_AUTO_H\n",
@ -122,7 +123,7 @@ bldErrRenderErrorAutoC(const Storage *const storageRepo, const BldErr bldErr)
}
strCatZ(
error,
error,
" NULL,\n"
"};\n");

View File

@ -291,7 +291,8 @@ bldHlpRenderHelpAutoC(const Storage *const storageRepo, const BldCfg bldCfg, con
// Convert buffer to bytes
const Buffer *const buffer = bldHlpRenderHelpAutoCCmp(bldCfg, bldHlp);
String *const help = strNewFmt(
String *const help = strCatFmt(
strNew(),
"%s"
"static const unsigned char helpData[%zu] =\n"
"{\n",

View File

@ -1023,7 +1023,7 @@ cmdArchiveGetAsync(void)
{
LOG_WARN_FMT("[%s] %s", errorTypeName(checkResult.errorType), strZ(checkResult.errorMessage));
String *message = strDup(checkResult.errorMessage);
String *message = strCat(strNew(), checkResult.errorMessage);
if (!strLstEmpty(checkResult.warnList))
strCatFmt(message, "\n%s", strZ(strLstJoin(checkResult.warnList, "\n")));

View File

@ -140,7 +140,7 @@ archivePushFile(
}
// Set archive destination initially to the archive file, this will be updated later for wal segments
String *archiveDestination = strDup(archiveFile);
String *archiveDestination = strCat(strNew(), archiveFile);
// Assume that all repos need a copy of the archive file
bool destinationCopyAny = true;

View File

@ -70,7 +70,7 @@ backupRegExp(BackupRegExpParam param)
String *result = NULL;
// Start the expression with the anchor, date/time regexp and full backup indicator
result = strNewZ("^" DATE_TIME_REGEX "F");
result = strCatZ(strNew(), "^" DATE_TIME_REGEX "F");
// Add the diff and/or incr expressions if requested
if (param.differential || param.incremental)

View File

@ -172,7 +172,7 @@ cmdBegin(void)
MEM_CONTEXT_TEMP_BEGIN()
{
// Basic info on command start
String *info = strNewFmt("%s command begin", strZ(cfgCommandRoleName()));
String *info = strCatFmt(strNew(), "%s command begin", strZ(cfgCommandRoleName()));
// Free the old option string if it exists. This is needed when more than one command is run in a row so an option
// string gets created for the new command.
@ -213,7 +213,7 @@ cmdEnd(int code, const String *errorMessage)
LOG_DETAIL_FMT("statistics: %s", strZ(jsonFromKv(statKv)));
// Basic info on command end
String *info = strNewFmt("%s command end: ", strZ(cfgCommandRoleName()));
String *info = strCatFmt(strNew(), "%s command end: ", strZ(cfgCommandRoleName()));
if (errorMessage == NULL)
{

View File

@ -238,7 +238,7 @@ helpRender(const Buffer *const helpData)
FUNCTION_LOG_PARAM(BUFFER, helpData);
FUNCTION_LOG_END();
String *result = strNewZ(PROJECT_NAME " " PROJECT_VERSION);
String *result = strCatZ(strNew(), PROJECT_NAME " " PROJECT_VERSION);
MEM_CONTEXT_TEMP_BEGIN()
{
@ -441,7 +441,8 @@ helpRender(const Buffer *const helpData)
ConfigOption optionId = varUInt(varLstGet(optionList, optionIdx));
// Get option summary and lower-case first letter if it does not appear to be part of an acronym
String *summary = strNewN(strZ(optionData[optionId].summary), strSize(optionData[optionId].summary) - 1);
String *summary = strCatN(
strNew(), optionData[optionId].summary, strSize(optionData[optionId].summary) - 1);
ASSERT(strSize(summary) > 1);
if (!isupper(strZ(summary)[1]) && !isdigit(strZ(summary)[1]))

View File

@ -1069,7 +1069,7 @@ formatTextDb(
}
}
String *resultCurrent = strNewZ("\n db (current)");
String *resultCurrent = strCatZ(strNew(), "\n db (current)");
bool displayCurrent = false;
for (unsigned int dbGrpIdx = backupDbGrpIdxMin; dbGrpIdx < backupDbGrpIdxMax; dbGrpIdx++)

View File

@ -1675,7 +1675,7 @@ restoreRecoveryConf(unsigned int pgVersion, const String *restoreLabel)
MEM_CONTEXT_TEMP_BEGIN()
{
result = strNewFmt("# Recovery settings generated by " PROJECT_NAME " restore on %s\n", strZ(restoreLabel));
result = strCatFmt(strNew(), "# Recovery settings generated by " PROJECT_NAME " restore on %s\n", strZ(restoreLabel));
// Output all recovery options
KeyValue *optionKv = restoreRecoveryOption(pgVersion);
@ -2073,7 +2073,7 @@ restoreJobResult(const Manifest *manifest, ProtocolParallelJob *job, RegExp *zer
bool zeroed = restoreFileZeroed(file->name, zeroExp);
bool copy = pckReadBoolP(protocolParallelJobResult(job));
String *log = strNewZ("restore");
String *log = strCatZ(strNew(), "restore");
// Note if file was zeroed (i.e. selective restore)
if (zeroed)
@ -2285,8 +2285,9 @@ cmdRestore(void)
restoreManifestValidate(jobData.manifest, backupData.backupSet);
// Log the backup set to restore. If the backup was online then append the time recovery will start from.
String *const message = strNewFmt(
"repo%u: restore backup set %s", cfgOptionGroupIdxToKey(cfgOptGrpRepo, backupData.repoIdx), strZ(backupData.backupSet));
String *const message = strCatFmt(
strNew(), "repo%u: restore backup set %s", cfgOptionGroupIdxToKey(cfgOptGrpRepo, backupData.repoIdx),
strZ(backupData.backupSet));
if (manifestData(jobData.manifest)->backupOptionOnline)
{

View File

@ -227,7 +227,7 @@ verifyInfoFile(const String *pathFileName, bool keepFile, const String *cipherPa
CATCH_ANY()
{
result.errorCode = errorCode();
String *errorMsg = strNewZ(errorMessage());
String *errorMsg = strCatZ(strNew(), errorMessage());
if (result.errorCode == errorTypeCode(&ChecksumError))
strCat(errorMsg, strNewFmt(" %s", strZ(pathFileName)));
@ -1252,7 +1252,7 @@ verifyRender(List *archiveIdResultList, List *backupResultList)
ASSERT(archiveIdResultList != NULL);
ASSERT(backupResultList != NULL);
String *result = strNewZ("Results:");
String *result = strCatZ(strNew(), "Results:");
// Render archive results
if (lstEmpty(archiveIdResultList))

View File

@ -88,7 +88,8 @@ exitErrorDetail(void)
FUNCTION_TEST_VOID();
FUNCTION_TEST_RETURN(
strNewFmt(
strCatFmt(
strNew(),
"--------------------------------------------------------------------\n"
"If SUBMITTING AN ISSUE please provide the following information:\n"
"\n"
@ -148,7 +149,7 @@ exitSafe(int result, bool error, SignalType signalType)
// On process terminate
if (result == errorTypeCode(&TermError))
{
errorMessage = strNewZ("terminated on signal ");
errorMessage = strCatZ(strNew(), "terminated on signal ");
// Terminate from a child
if (signalType == signalTypeNone)

View File

@ -95,7 +95,7 @@ httpHeaderAdd(HttpHeader *this, const String *key, const String *value)
{
MEM_CONTEXT_TEMP_BEGIN()
{
String *valueAppend = strDup(varStr(valueVar));
String *const valueAppend = strCat(strNew(), varStr(valueVar));
strCatZ(valueAppend, ", ");
strCatZ(valueAppend, strZ(value));
@ -177,7 +177,7 @@ httpHeaderRedact(const HttpHeader *this, const String *key)
String *
httpHeaderToLog(const HttpHeader *this)
{
String *result = strNewZ("{");
String *result = strCatZ(strNew(), "{");
const StringList *keyList = httpHeaderList(this);
for (unsigned int keyIdx = 0; keyIdx < strLstSize(keyList); keyIdx++)

View File

@ -291,7 +291,7 @@ httpQueryRender(const HttpQuery *this, HttpQueryRenderParam param)
String *
httpQueryToLog(const HttpQuery *this)
{
String *result = strNewZ("{");
String *result = strCatZ(strNew(), "{");
const StringList *keyList = httpQueryList(this);
for (unsigned int keyIdx = 0; keyIdx < strLstSize(keyList); keyIdx++)

View File

@ -96,7 +96,8 @@ httpRequestProcess(HttpRequest *this, bool waitForResponse, bool contentCache)
// Format the request and user agent
String *requestStr =
strNewFmt(
strCatFmt(
strNew(),
"%s %s%s%s " HTTP_VERSION CRLF_Z HTTP_HEADER_USER_AGENT ":" PROJECT_NAME "/" PROJECT_VERSION CRLF_Z,
strZ(httpRequestVerb(this)), strZ(httpRequestPath(this)), httpRequestQuery(this) == NULL ? "" : "?",
httpRequestQuery(this) == NULL ? "" : strZ(httpQueryRenderP(httpRequestQuery(this))));
@ -242,7 +243,7 @@ httpRequestError(const HttpRequest *this, HttpResponse *response)
ASSERT(response != NULL);
// Error code
String *error = strNewFmt("HTTP request failed with %u", httpResponseCode(response));
String *error = strCatFmt(strNew(), "HTTP request failed with %u", httpResponseCode(response));
// Add reason when present
if (strSize(httpResponseReason(response)) > 0)

View File

@ -787,7 +787,7 @@ jsonFromKvInternal(const KeyValue *kv)
ASSERT(kv != NULL);
String *result = strNewZ("{");
String *result = strCatZ(strNew(), "{");
MEM_CONTEXT_TEMP_BEGIN()
{

View File

@ -39,6 +39,21 @@ STRING_EXTERN(TRUE_STR, TRUE_Z);
STRING_EXTERN(Y_STR, Y_Z);
STRING_EXTERN(ZERO_STR, ZERO_Z);
/***********************************************************************************************************************************
Buffer macros
***********************************************************************************************************************************/
// Fixed size buffer allocated at the end of the object allocation
#define STR_FIXED_BUFFER (char *)(this + 1)
// Is the string using the fixed size buffer?
#define STR_IS_FIXED_BUFFER() (this->pub.buffer == STR_FIXED_BUFFER)
// Empty buffer
#define STR_EMPTY_BUFFER (EMPTY_STR->pub.buffer)
// Is the string using the empty buffer?
#define STR_IS_EMPTY_BUFFER() (this->pub.buffer == STR_EMPTY_BUFFER)
/***********************************************************************************************************************************
Maximum size of a string
***********************************************************************************************************************************/
@ -74,15 +89,38 @@ strNew(void)
{
.pub =
{
// A zero-length string is not very useful so assume this string is being created for appending and allocate extra space
.extra = STRING_EXTRA_MIN,
// Set empty so nothing is allocated until needed
.buffer = STR_EMPTY_BUFFER,
},
.memContext = memContextCurrent(),
};
// Allocate and assign string
this->pub.buffer = memNew(STRING_EXTRA_MIN + 1);
this->pub.buffer[0] = '\0';
FUNCTION_TEST_RETURN(this);
}
/***********************************************************************************************************************************
Create fixed size String
***********************************************************************************************************************************/
static String *
strNewFixed(const size_t size)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(SIZE, size);
FUNCTION_TEST_END();
CHECK_SIZE(size);
String *this = memNew(sizeof(String) + size + 1);
*this = (String)
{
.pub =
{
.size = (unsigned int)size,
.buffer = STR_FIXED_BUFFER,
},
.memContext = memContextCurrent(),
};
FUNCTION_TEST_RETURN(this);
}
@ -97,25 +135,12 @@ strNewZ(const char *const string)
ASSERT(string != NULL);
// Check size
size_t stringSize = strlen(string);
CHECK_SIZE(stringSize);
// Create object
String *this = memNew(sizeof(String));
String *this = strNewFixed(strlen(string));
*this = (String)
{
.pub =
{
.size = (unsigned int)stringSize,
},
.memContext = memContextCurrent(),
};
// Allocate and assign string
this->pub.buffer = memNew(strSize(this) + this->pub.extra + 1);
strcpy(this->pub.buffer, string);
// Assign string
strncpy(this->pub.buffer, string, strSize(this));
this->pub.buffer[strSize(this)] = '\0';
FUNCTION_TEST_RETURN(this);
}
@ -144,23 +169,10 @@ strNewBuf(const Buffer *buffer)
ASSERT(buffer != NULL);
// Check size
CHECK_SIZE(bufUsed(buffer));
// Create object
String *this = memNew(sizeof(String));
String *this = strNewFixed(bufUsed(buffer));
*this = (String)
{
.pub =
{
.size = (unsigned int)bufUsed(buffer),
},
.memContext = memContextCurrent(),
};
// Allocate and assign string
this->pub.buffer = memNew(strSize(this) + 1);
// Assign string
memcpy(this->pub.buffer, bufPtrConst(buffer), strSize(this));
this->pub.buffer[strSize(this)] = 0;
@ -178,24 +190,10 @@ strNewEncode(EncodeType type, const Buffer *buffer)
ASSERT(buffer != NULL);
// Check encoded size
size_t size = encodeToStrSize(type, bufUsed(buffer));
CHECK_SIZE(size);
// Create object
String *this = memNew(sizeof(String));
String *this = strNewFixed(encodeToStrSize(type, bufUsed(buffer)));
*this = (String)
{
.pub =
{
.size = (unsigned int)size,
},
.memContext = memContextCurrent(),
};
// Allocate and encode buffer
this->pub.buffer = memNew(strSize(this) + 1);
// Encode buffer
encodeToStr(type, bufPtrConst(buffer), bufUsed(buffer), this->pub.buffer);
FUNCTION_TEST_RETURN(this);
@ -211,26 +209,13 @@ strNewFmt(const char *format, ...)
ASSERT(format != NULL);
// Create object
String *this = memNew(sizeof(String));
*this = (String)
{
.memContext = memContextCurrent(),
};
// Determine how long the allocated string needs to be
// Determine how long the allocated string needs to be and create object
va_list argumentList;
va_start(argumentList, format);
size_t formatSize = (size_t)vsnprintf(NULL, 0, format, argumentList);
String *this = strNewFixed((size_t)vsnprintf(NULL, 0, format, argumentList));
va_end(argumentList);
// Check size
CHECK_SIZE(formatSize);
// Allocate and assign string
this->pub.size = (unsigned int)formatSize;
this->pub.buffer = memNew(strSize(this) + 1);
// Format string
va_start(argumentList, format);
vsnprintf(this->pub.buffer, strSize(this) + 1, format, argumentList);
va_end(argumentList);
@ -249,27 +234,13 @@ strNewN(const char *string, size_t size)
ASSERT(string != NULL);
// Check size
CHECK_SIZE(size);
// Create object
String *this = memNew(sizeof(String));
String *this = strNewFixed(size);
*this = (String)
{
.pub =
{
.size = (unsigned int)size,
},
.memContext = memContextCurrent(),
};
// Allocate and assign string
this->pub.buffer = memNew(strSize(this) + 1);
// Assign string
strncpy(this->pub.buffer, string, strSize(this));
this->pub.buffer[strSize(this)] = 0;
// Return buffer
FUNCTION_TEST_RETURN(this);
}
@ -351,6 +322,9 @@ strResize(String *this, size_t requested)
if (requested > this->pub.extra)
{
// Fixed size strings may not be resized
CHECK(!STR_IS_FIXED_BUFFER());
// Check size
CHECK_SIZE(strSize(this) + requested);
@ -363,7 +337,10 @@ strResize(String *this, size_t requested)
MEM_CONTEXT_BEGIN(this->memContext)
{
this->pub.buffer = memResize(this->pub.buffer, strSize(this) + this->pub.extra + 1);
if (STR_IS_EMPTY_BUFFER())
this->pub.buffer = memNew(strSize(this) + this->pub.extra + 1);
else
this->pub.buffer = memResize(this->pub.buffer, strSize(this) + this->pub.extra + 1);
}
MEM_CONTEXT_END();
}
@ -400,13 +377,16 @@ strCatZ(String *this, const char *cat)
// Determine length of string to append
size_t sizeGrow = strlen(cat);
// Ensure there is enough space to grow the string
strResize(this, sizeGrow);
if (sizeGrow != 0)
{
// Ensure there is enough space to grow the string
strResize(this, sizeGrow);
// Append the string
strcpy(this->pub.buffer + strSize(this), cat);
this->pub.size += (unsigned int)sizeGrow;
this->pub.extra -= (unsigned int)sizeGrow;
// Append the string
strcpy(this->pub.buffer + strSize(this), cat);
this->pub.size += (unsigned int)sizeGrow;
this->pub.extra -= (unsigned int)sizeGrow;
}
FUNCTION_TEST_RETURN(this);
}
@ -421,22 +401,41 @@ strCatZN(String *this, const char *cat, size_t size)
FUNCTION_TEST_END();
ASSERT(this != NULL);
ASSERT(cat != NULL);
// Ensure there is enough space to grow the string
strResize(this, size);
if (size != 0)
{
ASSERT(cat != NULL);
// Append the string
strncpy(this->pub.buffer + strSize(this), cat, size);
this->pub.buffer[strSize(this) + size] = '\0';
// Ensure there is enough space to grow the string
strResize(this, size);
// Update size/extra
this->pub.size += (unsigned int)size;
this->pub.extra -= (unsigned int)size;
// Append the string
strncpy(this->pub.buffer + strSize(this), cat, size);
this->pub.buffer[strSize(this) + size] = '\0';
// Update size/extra
this->pub.size += (unsigned int)size;
this->pub.extra -= (unsigned int)size;
}
FUNCTION_TEST_RETURN(this);
}
/**********************************************************************************************************************************/
String *
strCatBuf(String *const this, const Buffer *const buffer)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(STRING, this);
FUNCTION_TEST_PARAM(BUFFER, buffer);
FUNCTION_TEST_END();
ASSERT(this != NULL);
ASSERT(buffer != NULL);
FUNCTION_TEST_RETURN(strCatZN(this, (char *)bufPtrConst(buffer), bufUsed(buffer)));
}
/**********************************************************************************************************************************/
String *
strCatChr(String *this, char cat)
@ -473,16 +472,20 @@ strCatEncode(String *this, EncodeType type, const Buffer *buffer)
ASSERT(this != NULL);
ASSERT(buffer != NULL);
// Ensure there is enough space to grow the string
size_t encodeSize = encodeToStrSize(type, bufUsed(buffer));
strResize(this, encodeSize);
// Append the encoded string
encodeToStr(type, bufPtrConst(buffer), bufUsed(buffer), this->pub.buffer + strSize(this));
if (encodeSize != 0)
{
// Ensure there is enough space to grow the string
strResize(this, encodeSize);
// Update size/extra
this->pub.size += (unsigned int)encodeSize;
this->pub.extra -= (unsigned int)encodeSize;
// Append the encoded string
encodeToStr(type, bufPtrConst(buffer), bufUsed(buffer), this->pub.buffer + strSize(this));
// Update size/extra
this->pub.size += (unsigned int)encodeSize;
this->pub.extra -= (unsigned int)encodeSize;
}
FUNCTION_TEST_RETURN(this);
}
@ -505,16 +508,19 @@ strCatFmt(String *this, const char *format, ...)
size_t sizeGrow = (size_t)vsnprintf(NULL, 0, format, argumentList);
va_end(argumentList);
// Ensure there is enough space to grow the string
strResize(this, sizeGrow);
if (sizeGrow != 0)
{
// Ensure there is enough space to grow the string
strResize(this, sizeGrow);
// Append the formatted string
va_start(argumentList, format);
vsnprintf(this->pub.buffer + strSize(this), sizeGrow + 1, format, argumentList);
va_end(argumentList);
// Append the formatted string
va_start(argumentList, format);
vsnprintf(this->pub.buffer + strSize(this), sizeGrow + 1, format, argumentList);
va_end(argumentList);
this->pub.size += (unsigned int)sizeGrow;
this->pub.extra -= (unsigned int)sizeGrow;
this->pub.size += (unsigned int)sizeGrow;
this->pub.extra -= (unsigned int)sizeGrow;
}
FUNCTION_TEST_RETURN(this);
}
@ -695,9 +701,8 @@ strUpper(String *this)
ASSERT(this != NULL);
if (strSize(this) > 0)
for (size_t idx = 0; idx <= strSize(this); idx++)
this->pub.buffer[idx] = (char)toupper(this->pub.buffer[idx]);
for (size_t idx = 0; idx < strSize(this); idx++)
this->pub.buffer[idx] = (char)toupper(this->pub.buffer[idx]);
FUNCTION_TEST_RETURN(this);
}
@ -712,9 +717,8 @@ strLower(String *this)
ASSERT(this != NULL);
if (strSize(this) > 0)
for (size_t idx = 0; idx <= strSize(this); idx++)
this->pub.buffer[idx] = (char)tolower(this->pub.buffer[idx]);
for (size_t idx = 0; idx < strSize(this); idx++)
this->pub.buffer[idx] = (char)tolower(this->pub.buffer[idx]);
FUNCTION_TEST_RETURN(this);
}
@ -947,20 +951,13 @@ strTrim(String *this)
if (begin != this->pub.buffer || newSize < strSize(this))
{
// Calculate new size
// Calculate new size and extra
this->pub.extra = (unsigned int)(strSize(this) - newSize);
this->pub.size = (unsigned int)newSize;
// Move the substr to the beginning of the buffer
memmove(this->pub.buffer, begin, strSize(this));
this->pub.buffer[strSize(this)] = 0;
this->pub.extra = 0;
MEM_CONTEXT_BEGIN(this->memContext)
{
// Resize the buffer
this->pub.buffer = memResize(this->pub.buffer, strSize(this) + 1);
}
MEM_CONTEXT_END();
}
}
@ -1005,16 +1002,9 @@ strTrunc(String *this, int idx)
if (strSize(this) > 0)
{
// Reset the size to end at the index
this->pub.size = (unsigned int)(idx);
this->pub.extra = (unsigned int)(strSize(this) - (size_t)idx);
this->pub.size = (unsigned int)idx;
this->pub.buffer[strSize(this)] = 0;
this->pub.extra = 0;
MEM_CONTEXT_BEGIN(this->memContext)
{
// Resize the buffer
this->pub.buffer = memResize(this->pub.buffer, strSize(this) + 1);
}
MEM_CONTEXT_END();
}
FUNCTION_TEST_RETURN(this);
@ -1095,7 +1085,9 @@ strFree(String *this)
{
MEM_CONTEXT_BEGIN(this->memContext)
{
memFree(this->pub.buffer);
if (!STR_IS_EMPTY_BUFFER() && !STR_IS_FIXED_BUFFER())
memFree(this->pub.buffer);
memFree(this);
}
MEM_CONTEXT_END();

View File

@ -41,31 +41,38 @@ typedef struct String String;
/***********************************************************************************************************************************
Constructors
Only strings created with strNew() can use strCat*() functions. If a string needs to be concatenated then start with, .e.g.:
String *example = strCatZ(strNew(), "example");
This syntax signals that the string will be modified so the string is allocated separately from the object so it can be resized when
needed. Most strings never need to be modified and can be stored more efficiently by allocating their memory with the object.
***********************************************************************************************************************************/
// Create a new empty string
// Create a new empty string for concatenation
String *strNew(void);
// Create a new string from a zero-terminated string
// Create a new fixed length string from a zero-terminated string
String *strNewZ(const char *const string);
// Create a new string from a buffer. If the buffer has a NULL character this may not work as expected. All the data will be copied
// but only the data before the NULL character will be used as a string.
// Create a new fixed length string from a buffer. If the buffer has a NULL character this may not work as expected. All the data
// will be copied but only the data before the NULL character will be used as a string.
String *strNewBuf(const Buffer *buffer);
// Create a new string by converting the double value
// Create a new fixed length string by converting the double value
String *strNewDbl(double value);
// Create a new string encoded with the specified type (e.g. encodeBase64) from a buffer
// Create a new fixed length string encoded with the specified type (e.g. encodeBase64) from a buffer
String *strNewEncode(EncodeType type, const Buffer *buffer);
// Create a new string from a format string with parameters (i.e. sprintf)
// Create a new fixed length string from a format string with parameters (i.e. sprintf)
String *strNewFmt(const char *format, ...) __attribute__((format(printf, 1, 2)));
// Create a new string from a string with a specific length. The string may or may not be zero-terminated but we'll use that
// nomenclature since we're not concerned about the end of the string.
// Create a new fixed length string from a zero-terminated string with a specific length. The string may or may not be
// zero-terminated but we'll use that nomenclature since we're not concerned about the end of the string.
String *strNewN(const char *string, size_t size);
// Duplicate a string
// Create a new fixed length string from a string
String *strDup(const String *this);
/***********************************************************************************************************************************
@ -110,6 +117,9 @@ bool strBeginsWithZ(const String *this, const char *beginsWith);
String *strCat(String *this, const String *cat);
String *strCatZ(String *this, const char *cat);
// Append a buffer
String *strCatBuf(String *this, const Buffer *buffer);
// Append a character
String *strCatChr(String *this, char cat);
@ -123,6 +133,13 @@ String *strCatFmt(String *this, const char *format, ...) __attribute__((format(p
// N is <= the end of the string being concatenated.
String *strCatZN(String *this, const char *cat, size_t size);
__attribute__((always_inline)) static inline String *
strCatN(String *this, const String *const cat, const size_t size)
{
ASSERT_INLINE(cat != NULL);
return strCatZN(this, strZ(cat), size);
}
// Return the index to the location of the first occurrence of a character within a string, else -1
int strChr(const String *this, char chr);

View File

@ -808,19 +808,36 @@ varNewStr(const String *data)
FUNCTION_TEST_PARAM(STRING, data);
FUNCTION_TEST_END();
// Allocate memory for the variant and set the type and data
VariantString *this = memNew(sizeof(VariantString));
// Allocate memory for the variant
VariantString *this = memNew(sizeof(VariantString) + (data != NULL ? sizeof(StringPub) + strSize(data) + 1 : 0));
*this = (VariantString)
{
.pub =
{
.type = varTypeString,
.data = strDup(data),
},
.memContext = memContextCurrent(),
};
if (data != NULL)
{
// Point the String after the VariantString struct
StringPub *const pubData = (StringPub *)(this + 1);
this->pub.data = (String *)pubData;
// Assign the string size and buffer (after StringPub struct)
*pubData = (StringPub)
{
.size = (unsigned int)strSize(data),
.buffer = (char *)(pubData + 1),
};
// Assign the string
strncpy(pubData->buffer, strZ(data), strSize(data));
pubData->buffer[strSize(data)] = '\0';
}
FUNCTION_TEST_RETURN((Variant *)this);
}
@ -831,7 +848,7 @@ varNewStrZ(const char *data)
FUNCTION_TEST_PARAM(STRINGZ, data);
FUNCTION_TEST_END();
FUNCTION_TEST_RETURN(varNewStr(data == NULL ? NULL : strNewZ(data)));
FUNCTION_TEST_RETURN(varNewStr(data == NULL ? NULL : STR(data)));
}
/**********************************************************************************************************************************/
@ -1031,7 +1048,6 @@ varFree(Variant *this)
case varTypeString:
memContext = ((VariantString *)this)->memContext;
strFree(((VariantString *)this)->pub.data);
break;
case varTypeUInt:

View File

@ -333,7 +333,8 @@ cfgLoadLogFile(void)
MEM_CONTEXT_TEMP_BEGIN()
{
// Construct log filename prefix
String *logFile = strNewFmt(
String *logFile = strCatFmt(
strNew(),
"%s/%s-%s", strZ(cfgOptionStr(cfgOptLogPath)),
cfgOptionTest(cfgOptStanza) ? strZ(cfgOptionStr(cfgOptStanza)): "all", cfgCommandName());

View File

@ -515,7 +515,7 @@ cfgParseCommandRoleName(const ConfigCommand commandId, const ConfigCommandRole c
FUNCTION_TEST_PARAM(STRING, separator);
FUNCTION_TEST_END();
String *result = strNewZ(cfgParseCommandName(commandId));
String *result = strCatZ(strNew(), cfgParseCommandName(commandId));
if (commandRoleId != cfgCmdRoleMain)
strCatFmt(result, "%s%s", strZ(separator), strZ(cfgParseCommandRoleStr(commandRoleId)));
@ -1097,14 +1097,14 @@ cfgFileLoad( // NOTE: Pas
// Convert the contents of the file buffer to the config string object
if (buffer != NULL)
result = strNewBuf(buffer);
result = strCatBuf(strNew(), buffer);
else if (strEq(configFileName, optConfigDefaultCurrent))
{
// If config is current default and it was not found, attempt to load the config file from the old default location
buffer = storageGetP(storageNewReadP(storage, origConfigDefault, .ignoreMissing = !configRequired));
if (buffer != NULL)
result = strNewBuf(buffer);
result = strCatBuf(strNew(), buffer);
}
}

View File

@ -277,7 +277,8 @@ dbBackupStartQuery(unsigned int pgVersion, bool startFast)
FUNCTION_TEST_END();
// Build query to return start lsn and WAL segment name
String *result = strNewFmt(
String *result = strCatFmt(
strNew(),
"select lsn::text as lsn,\n"
" pg_catalog.pg_%sfile_name(lsn)::text as wal_segment_name\n"
" from pg_catalog.pg_start_backup('" PROJECT_NAME " backup started at ' || current_timestamp",
@ -372,7 +373,8 @@ dbBackupStopQuery(unsigned int pgVersion)
FUNCTION_TEST_END();
// Build query to return start lsn and WAL segment name
String *result = strNewFmt(
String *result = strCatFmt(
strNew(),
"select lsn::text as lsn,\n"
" pg_catalog.pg_%sfile_name(lsn)::text as wal_segment_name",
strZ(pgWalName(pgVersion)));
@ -510,7 +512,8 @@ dbReplayWait(Db *this, const String *targetLsn, TimeMSec timeout)
do
{
// Build the query
String *query = strNewFmt(
String *query = strCatFmt(
strNew(),
"select replayLsn::text,\n"
" (replayLsn > '%s')::bool as targetReached",
strZ(targetLsn));

View File

@ -506,7 +506,7 @@ infoLoad(const String *error, InfoLoadCallback *callbackFunction, void *callback
if (loadErrorType == NULL)
{
loadErrorType = errorType();
loadErrorMessage = strNewFmt("%s:", strZ(error));
loadErrorMessage = strCatFmt(strNew(), "%s:", strZ(error));
}
// Else if the error type is different
else if (loadErrorType != errorType())

View File

@ -2847,7 +2847,7 @@ manifestTargetPath(const Manifest *this, const ManifestTarget *target)
MEM_CONTEXT_TEMP_BEGIN()
{
String *pgPath = strPath(manifestPathPg(target->name));
String *pgPath = strCat(strNew(), strPath(manifestPathPg(target->name)));
if (strSize(pgPath) != 0)
strCatZ(pgPath, "/");

View File

@ -101,7 +101,7 @@ pgClientEscape(const String *string)
ASSERT(string != NULL);
String *result = strNewZ("'");
String *result = strCatZ(strNew(), "'");
// Iterate all characters in the string
for (unsigned stringIdx = 0; stringIdx < strSize(string); stringIdx++)
@ -134,7 +134,7 @@ pgClientOpen(PgClient *this)
MEM_CONTEXT_TEMP_BEGIN()
{
// Base connection string
String *connInfo = strNewFmt("dbname=%s port=%u", strZ(pgClientEscape(this->database)), this->port);
String *connInfo = strCatFmt(strNew(), "dbname=%s port=%u", strZ(pgClientEscape(this->database)), this->port);
// Add user if specified
if (this->user != NULL)

View File

@ -209,7 +209,7 @@ protocolServerProcess(
if (errType == NULL)
{
errType = errorType();
errMessage = strNewZ(errorMessage());
errMessage = strCatZ(strNew(), errorMessage());
errMessageFirst = strNewZ(errorMessage());
errStackTrace = strNewZ(errorStackTrace());
}

View File

@ -168,7 +168,7 @@ storageGcsAuthJwt(StorageGcs *this, time_t timeBegin)
FUNCTION_TEST_END();
// Static header with dot delimiter
String *result = strNewZ("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.");
String *result = strCatZ(strNew(), "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.");
MEM_CONTEXT_TEMP_BEGIN()
{
@ -382,7 +382,7 @@ storageGcsRequestAsync(StorageGcs *this, const String *verb, StorageGcsRequestAs
MEM_CONTEXT_TEMP_BEGIN()
{
// Generate path
String *path = strNewFmt("%s/storage/v1/b", param.upload ? "/upload" : "");
String *path = strCatFmt(strNew(), "%s/storage/v1/b", param.upload ? "/upload" : "");
if (!param.noBucket)
strCatFmt(path, "/%s/o", strZ(this->bucket));

View File

@ -306,9 +306,9 @@ storageRepoPathExpression(const String *expression, const String *path)
{
// Construct the base path
if (storageHelper.stanza != NULL)
result = strNewFmt(STORAGE_PATH_ARCHIVE "/%s", strZ(storageHelper.stanza));
result = strCatFmt(strNew(), STORAGE_PATH_ARCHIVE "/%s", strZ(storageHelper.stanza));
else
result = strNewZ(STORAGE_PATH_ARCHIVE);
result = strCatZ(strNew(), STORAGE_PATH_ARCHIVE);
// If a subpath should be appended, determine if it is WAL path, else just append the subpath
if (path != NULL)
@ -326,9 +326,9 @@ storageRepoPathExpression(const String *expression, const String *path)
{
// Construct the base path
if (storageHelper.stanza != NULL)
result = strNewFmt(STORAGE_PATH_BACKUP "/%s", strZ(storageHelper.stanza));
result = strCatFmt(strNew(), STORAGE_PATH_BACKUP "/%s", strZ(storageHelper.stanza));
else
result = strNewZ(STORAGE_PATH_BACKUP);
result = strCatZ(strNew(), STORAGE_PATH_BACKUP);
// Append subpath if provided
if (path != NULL)

View File

@ -174,8 +174,8 @@ storageS3Auth(
const StringList *headerList = strLstSort(strLstDup(httpHeaderList(httpHeader)), sortOrderAsc);
String *signedHeaders = NULL;
String *canonicalRequest = strNewFmt(
"%s\n%s\n%s\n", strZ(verb), strZ(path), query == NULL ? "" : strZ(httpQueryRenderP(query)));
String *canonicalRequest = strCatFmt(
strNew(), "%s\n%s\n%s\n", strZ(verb), strZ(path), query == NULL ? "" : strZ(httpQueryRenderP(query)));
for (unsigned int headerIdx = 0; headerIdx < strLstSize(headerList); headerIdx++)
{
@ -189,7 +189,7 @@ storageS3Auth(
strCatFmt(canonicalRequest, "%s:%s\n", strZ(headerKeyLower), strZ(httpHeaderGet(httpHeader, headerKey)));
if (signedHeaders == NULL)
signedHeaders = strDup(headerKeyLower);
signedHeaders = strCat(strNew(), headerKeyLower);
else
strCatFmt(signedHeaders, ";%s", strZ(headerKeyLower));
}

View File

@ -349,7 +349,7 @@ hrnLogReplace(void)
}
// Build replacement string. If versioned then append the version number.
String *replace = strNewFmt("[%s", strZ(logReplace->replacement));
String *replace = strCatFmt(strNew(), "[%s", strZ(logReplace->replacement));
if (logReplace->version)
{

View File

@ -188,7 +188,7 @@ testStorageGet(const Storage *const storage, const char *const file, const char
ASSERT(storage != NULL);
ASSERT(file != NULL);
String *fileFull = storagePathP(storage, STR(file));
String *fileFull = strCat(strNew(), storagePathP(storage, STR(file)));
// Add compression extension if one exists
compressExtCat(fileFull, param.compressType);
@ -433,7 +433,7 @@ hrnStoragePut(
ASSERT(file != NULL);
// Add compression extension to file name
String *fileStr = strNewZ(file);
String *fileStr = strCatZ(strNew(), file);
compressExtCat(fileStr, param.compressType);
// Create file

View File

@ -876,7 +876,7 @@ testRun(void)
TEST_TITLE("encrypted/compressed file in backup");
// Create a compressed encrypted repo file in backup
filePathName = strNewZ(STORAGE_REPO_BACKUP "/testfile");
filePathName = strCatZ(strNew(), STORAGE_REPO_BACKUP "/testfile");
HRN_STORAGE_PUT_Z(
storageRepoWrite(), strZ(filePathName), fileContents, .compressType = compressTypeGz, .cipherType = cipherTypeAes256Cbc,
.cipherPass = "pass");

View File

@ -370,7 +370,7 @@ testRun(void)
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("compressExtCat()");
String *file = strNewZ("file");
String *file = strCatZ(strNew(), "file");
TEST_RESULT_VOID(compressExtCat(file, compressTypeGz), "cat gz ext");
TEST_RESULT_STR_Z(file, "file.gz", " check gz ext");

View File

@ -42,6 +42,7 @@ testRun(void)
TEST_ERROR(CHECK_SIZE(STRING_SIZE_MAX + 1), AssertError, "string size must be <= 1073741824 bytes");
String *string = strNewZ("static string");
TEST_RESULT_BOOL(string->pub.buffer == (char *)(string + 1), true, "string has fixed buffer");
TEST_RESULT_STR_Z(string, "static string", "new with static string");
TEST_RESULT_UINT(strSize(string), 13, "check size");
TEST_RESULT_BOOL(strEmpty(string), false, "is not empty");
@ -77,8 +78,10 @@ testRun(void)
TEST_TITLE("empty string is allocated extra space");
TEST_ASSIGN(string, strNew(), "new empty string");
TEST_RESULT_UINT(string->pub.size, 0, " check size");
TEST_RESULT_UINT(string->pub.extra, 64, " check extra");
TEST_RESULT_UINT(string->pub.size, 0, "check size");
TEST_RESULT_UINT(string->pub.extra, 0, "check extra");
TEST_RESULT_PTR(string->pub.buffer, EMPTY_STR->pub.buffer, "check buffer");
TEST_RESULT_VOID(strFree(string), "free string");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("strNewEncode()");
@ -122,23 +125,34 @@ testRun(void)
// *****************************************************************************************************************************
if (testBegin("strCat*()"))
{
String *string = strNewZ("XXXX");
String *string = strCatZ(strNew(), "XXX");
String *string2 = strNewZ("ZZZZ");
TEST_RESULT_STR_Z(strCat(string, STRDEF("YYYY")), "XXXXYYYY", "cat string");
TEST_RESULT_STR_Z(strCatN(string, STRDEF("XX"), 1), "XXXX", "cat N chars");
TEST_RESULT_UINT(string->pub.extra, 60, "check extra");
TEST_RESULT_STR_Z(strCatFmt(string, "%05d", 777), "XXXXYYYY00777", "cat formatted string");
TEST_RESULT_STR_Z(strCatZ(string, ""), "XXXX", "cat empty string");
TEST_RESULT_UINT(string->pub.extra, 60, "check extra");
TEST_RESULT_STR_Z(strCatEncode(string, encodeBase64, BUFSTRDEF("")), "XXXX", "cat empty encode");
TEST_RESULT_UINT(string->pub.extra, 60, "check extra");
TEST_RESULT_STR_Z(strCat(string, STRDEF("YYYY")), "XXXXYYYY", "cat string");
TEST_RESULT_UINT(string->pub.extra, 56, "check extra");
TEST_RESULT_STR_Z(strCatZN(string, NULL, 0), "XXXXYYYY", "cat 0");
TEST_RESULT_UINT(string->pub.extra, 56, "check extra");
TEST_RESULT_STR_Z(strCatBuf(string, BUFSTRDEF("?")), "XXXXYYYY?", "cat buf");
TEST_RESULT_UINT(string->pub.extra, 55, "check extra");
TEST_RESULT_STR_Z(strCatChr(string, '!'), "XXXXYYYY00777!", "cat chr");
TEST_RESULT_UINT(string->pub.extra, 54, "check extra");
TEST_RESULT_STR_Z(strCatFmt(string, "%05d", 777), "XXXXYYYY?00777", "cat formatted string");
TEST_RESULT_UINT(string->pub.extra, 50, "check extra");
TEST_RESULT_STR_Z(strCatChr(string, '!'), "XXXXYYYY?00777!", "cat chr");
TEST_RESULT_UINT(string->pub.extra, 49, "check extra");
TEST_RESULT_STR_Z(
strCatZN(string, "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$*", 55),
"XXXXYYYY00777!$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", "cat chr");
TEST_RESULT_UINT(string->pub.extra, 34, "check extra");
"XXXXYYYY?00777!$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$", "cat chr");
TEST_RESULT_UINT(string->pub.extra, 35, "check extra");
TEST_RESULT_STR_Z(
strCatEncode(string, encodeBase64, BUFSTRDEF("zzzzz")),
"XXXXYYYY00777!$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$enp6eno=", "cat encode");
TEST_RESULT_UINT(string->pub.extra, 26, "check extra");
"XXXXYYYY?00777!$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$enp6eno=", "cat encode");
TEST_RESULT_UINT(string->pub.extra, 27, "check extra");
TEST_RESULT_VOID(strFree(string), "free string");
TEST_RESULT_STR_Z(string2, "ZZZZ", "check unaltered string");
}
@ -262,7 +276,7 @@ testRun(void)
TEST_RESULT_INT(strChr(STRDEF("abcd"), 'i'), -1, "i not found");
TEST_RESULT_INT(strChr(STRDEF(""), 'x'), -1, "empty string - x not found");
String *val = strNewZ("abcdef");
String *val = strCatZ(strNew(), "abcdef");
TEST_ERROR(
strTrunc(val, (int)(strSize(val) + 1)), AssertError,
"assertion 'idx >= 0 && (size_t)idx <= strSize(this)' failed");

View File

@ -221,7 +221,7 @@ testRun(void)
{
CHECK(TEST_SCALE <= 10000);
String *iniStr = strNewZ("[section1]\n");
String *iniStr = strCatZ(strNew(), "[section1]\n");
unsigned int iniMax = 100000 * (unsigned int)TEST_SCALE;
for (unsigned int keyIdx = 0; keyIdx < iniMax; keyIdx++)

View File

@ -40,7 +40,7 @@ typedef struct TestRequestParam
static void
testRequest(IoWrite *write, const char *verb, const char *path, TestRequestParam param)
{
String *request = strNewFmt("%s /" TEST_ACCOUNT "/" TEST_CONTAINER, verb);
String *request = strCatFmt(strNew(), "%s /" TEST_ACCOUNT "/" TEST_CONTAINER, verb);
// When SAS spit out the query and merge in the SAS key
if (driver->sasKey != NULL)
@ -125,7 +125,7 @@ testResponse(IoWrite *write, TestResponseParam param)
param.code = param.code == 0 ? 200 : param.code;
// Output header and code
String *response = strNewFmt("HTTP/1.1 %u ", param.code);
String *response = strCatFmt(strNew(), "HTTP/1.1 %u ", param.code);
// Add reason for some codes
switch (param.code)

View File

@ -82,7 +82,8 @@ typedef struct TestRequestParam
static void
testRequest(IoWrite *write, const char *verb, TestRequestParam param)
{
String *request = strNewFmt("%s %s/storage/v1/b%s", verb, param.upload ? "/upload" : "", param.noBucket ? "" : "/bucket/o");
String *request = strCatFmt(
strNew(), "%s %s/storage/v1/b%s", verb, param.upload ? "/upload" : "", param.noBucket ? "" : "/bucket/o");
// Add object
if (param.object != NULL)
@ -140,7 +141,7 @@ testResponse(IoWrite *write, TestResponseParam param)
param.code = param.code == 0 ? 200 : param.code;
// Output header and code
String *response = strNewFmt("HTTP/1.1 %u ", param.code);
String *response = strCatFmt(strNew(), "HTTP/1.1 %u ", param.code);
// Add reason for some codes
switch (param.code)
@ -335,7 +336,8 @@ testRun(void)
const char *const preamble = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=";
const String *const jwt = storageGcsAuthJwt(((StorageGcs *)storageDriver(storage)), time(NULL));
String *const authRequest = strNewFmt(
String *const authRequest = strCatFmt(
strNew(),
"POST /token HTTP/1.1\r\n"
"user-agent:" PROJECT_NAME "/" PROJECT_VERSION "\r\n"
"content-length:%zu\r\n"

View File

@ -51,7 +51,7 @@ testRequest(IoWrite *write, Storage *s3, const char *verb, const char *path, Tes
}
// Add request
String *request = strNewFmt("%s %s HTTP/1.1\r\nuser-agent:" PROJECT_NAME "/" PROJECT_VERSION "\r\n", verb, path);
String *request = strCatFmt(strNew(), "%s %s HTTP/1.1\r\nuser-agent:" PROJECT_NAME "/" PROJECT_VERSION "\r\n", verb, path);
// Add authorization header when s3 service
if (s3 != NULL)
@ -142,7 +142,7 @@ testResponse(IoWrite *write, TestResponseParam param)
param.code = param.code == 0 ? 200 : param.code;
// Output header and code
String *response = strNewFmt("HTTP/%s %u ", param.http == NULL ? "1.1" : param.http, param.code);
String *response = strCatFmt(strNew(), "HTTP/%s %u ", param.http == NULL ? "1.1" : param.http, param.code);
// Add reason for some codes
switch (param.code)