• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

demmings / gsSQL / 19122082321

06 Nov 2025 01:43AM UTC coverage: 95.352% (+0.03%) from 95.319%
19122082321

push

github

demmings
SELF JOIN lint changes - Sonarcube

Not all suggestions implemented as many caused issues.  Others are just not supported in the current implementation of Javascript in Google Apps Script.

1589 of 1680 branches covered (94.58%)

Branch coverage included in aggregate %.

248 of 253 new or added lines in 8 files covered. (98.02%)

13141 of 13768 relevant lines covered (95.45%)

280.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.34
/src/ScriptSettings.js
1
/*  *** DEBUG START ***
1✔
2
//  Remove comments for testing in NODE
1✔
3
export { ScriptSettings, PropertyData };
1✔
4
import { CacheFinance } from "../CacheFinance.js";
1✔
5
import { PropertiesService } from "../GasMocks.js";
1✔
6

1✔
7
class Logger {
1✔
8
    static log(msg) {
1✔
9
        console.log(msg);
×
10
    }
×
11
}
1✔
12

1✔
13
//  *** DEBUG END ***/
1✔
14

1✔
15
/** @classdesc 
1✔
16
 * Stores settings for the SCRIPT.  Long term cache storage for small tables.  */
1✔
17
class ScriptSettings {      //  skipcq: JS-0128
1✔
18
    /**
1✔
19
     * For storing cache data for very long periods of time.
1✔
20
     */
1✔
21
    constructor() {
1✔
22
        this.scriptProperties = PropertiesService.getScriptProperties();
3✔
23
    }
3✔
24

1✔
25
    /**
1✔
26
     * Get script property using key.  If not found, returns null.
1✔
27
     * @param {String} propertyKey 
1✔
28
     * @returns {any}
1✔
29
     */
1✔
30
    get(propertyKey) {
1✔
31
        const myData = this.scriptProperties.getProperty(propertyKey);
6✔
32

6✔
33
        if (myData === null)
6✔
34
            return null;
6✔
35

4✔
36
        /** @type {PropertyData} */
4✔
37
        const myPropertyData = JSON.parse(myData);
4✔
38

4✔
39
        if (PropertyData.isExpired(myPropertyData))
4✔
40
        {
6!
41
            this.delete(propertyKey);
×
42
            return null;
×
43
        }
×
44

4✔
45
        return PropertyData.getData(myPropertyData);
4✔
46
    }
6✔
47

1✔
48
    /**
1✔
49
     * Put data into our PROPERTY cache, which can be held for long periods of time.
1✔
50
     * @param {String} propertyKey - key to finding property data.
1✔
51
     * @param {any} propertyData - value.  Any object can be saved..
1✔
52
     * @param {Number} daysToHold - number of days to hold before item is expired.
1✔
53
     */
1✔
54
    put(propertyKey, propertyData, daysToHold = 1) {
1✔
55
        //  Create our object with an expiry time.
3✔
56
        const objData = new PropertyData(propertyData, daysToHold);
3✔
57

3✔
58
        //  Our property needs to be a string
3✔
59
        const jsonData = JSON.stringify(objData);
3✔
60

3✔
61
        try {
3✔
62
            this.scriptProperties.setProperty(propertyKey, jsonData);
3✔
63
        }
3✔
64
        catch (ex) {
3!
65
            throw new Error("Cache Limit Exceeded.  Long cache times have limited storage available.  Only cache small tables for long periods.");
×
66
        }
×
67
    }
3✔
68

1✔
69
    /**
1✔
70
     * @param {Object} propertyDataObject 
1✔
71
     * @param {Number} daysToHold 
1✔
72
     */
1✔
73
    putAll(propertyDataObject, daysToHold = 1) {
1✔
74
        const keys = Object.keys(propertyDataObject);
1✔
75
        // keys.forEach(key => this.put(key, propertyDataObject[key], daysToHold));
1✔
76
        for (const key of keys) {
1✔
77
            this.put(key, propertyDataObject[key], daysToHold);
1✔
78
        }
1✔
79
    }
1✔
80

1✔
81
    /**
1✔
82
     * Puts list of data into cache using one API call.  Data is converted to JSON before it is updated.
1✔
83
     * @param {String[]} cacheKeys 
1✔
84
     * @param {any[]} newCacheData 
1✔
85
     * @param {Number} daysToHold
1✔
86
     */
1✔
87
    static putAllKeysWithData(cacheKeys, newCacheData, daysToHold = 7) {
1✔
88
        const bulkData = {};
×
89

×
90
        for (let i = 0; i < cacheKeys.length; i++) {
×
91
            //  Create our object with an expiry time.
×
92
            const objData = new PropertyData(newCacheData[i], daysToHold);
×
93

×
94
            //  Our property needs to be a string
×
95
            bulkData[cacheKeys[i]] = JSON.stringify(objData);
×
96
        }
×
97

×
98
        PropertiesService.getScriptProperties().setProperties(bulkData);
×
99
    }
×
100

1✔
101
    /**
1✔
102
     * Returns ALL cached data for each key value requested. 
1✔
103
     * Only 1 API call is made, so much faster than retrieving single values.
1✔
104
     * @param {String[]} cacheKeys 
1✔
105
     * @returns {any[]}
1✔
106
     */
1✔
107
    static getAll(cacheKeys) {
1✔
108
        const values = [];
×
109

×
110
        if (cacheKeys.length === 0) {
×
111
            return values;
×
112
        }
×
113
        
×
114
        const allProperties = PropertiesService.getScriptProperties().getProperties();
×
115

×
116
        //  Removing properties is very slow, so remove only 1 at a time.  This is enough as this function is called frequently.
×
117
        ScriptSettings.expire(false, 1, allProperties);
×
118

×
119
        for (const key of cacheKeys) {
×
120
            const myData = allProperties[key];
×
121

×
NEW
122
            if (myData === undefined) {
×
123
                values.push(null);
×
124
            }
×
125
            else {
×
126
                /** @type {PropertyData} */
×
127
                const myPropertyData = JSON.parse(myData);
×
128

×
129
                if (PropertyData.isExpired(myPropertyData)) {
×
130
                    values.push(null);
×
131
                    PropertiesService.getScriptProperties().deleteProperty(key);
×
132
                    Logger.log(`Delete expired Script Property Key=${key}`);
×
133
                }
×
134
                else {
×
135
                    values.push(PropertyData.getData(myPropertyData));
×
136
                }
×
137
            }
×
138
        }
×
139

×
140
        return values;
×
141
    }
×
142

1✔
143
    /**
1✔
144
     * Removes script settings that have expired.
1✔
145
     * @param {Boolean} deleteAll - true - removes ALL script settings regardless of expiry time.
1✔
146
     * @param {Number} maxDelete - maximum number of items to delete that are expired.
1✔
147
     * @param {Object} allPropertiesObject - All properties already loaded.  If null, will load iteself.
1✔
148
     */
1✔
149
    static expire(deleteAll, maxDelete = 999, allPropertiesObject = null) {
1✔
150
        const allProperties = allPropertiesObject === null ? PropertiesService.getScriptProperties().getProperties() : allPropertiesObject;
2!
151
        const allKeys = Object.keys(allProperties);
2✔
152
        let deleteCount = 0;
2✔
153

2✔
154
        for (const key of allKeys) {
2✔
155
            let propertyValue = null;
2✔
156
            try {
2✔
157
                propertyValue = JSON.parse(allProperties[key]);
2✔
158
            }
2✔
159
            catch (e) {
2✔
160
                //  A property that is NOT cached by CACHEFINANCE
2✔
161
                continue;
2✔
162
            }
2✔
163

×
164
            const propertyOfThisApplication = propertyValue?.expiry !== undefined;
2✔
165

2✔
166
            if (propertyOfThisApplication && (PropertyData.isExpired(propertyValue) || deleteAll)) {
2!
167
                PropertiesService.getScriptProperties().deleteProperty(key);
×
168
                delete allProperties[key];
×
169

×
170
                //  There is no way to iterate existing from 'short' cache, so we assume there is a
×
171
                //  matching short cache entry and attempt to delete.
×
172
                CacheFinance.deleteFromShortCache(key);
×
173

×
174
                Logger.log(`Removing expired SCRIPT PROPERTY: key=${key}`);
×
175

×
176
                deleteCount++;
×
177
            }
×
178

×
179
            if (deleteCount >= maxDelete) {
×
180
                return;
×
181
            }
×
182
        }
2✔
183
    }
2✔
184

1✔
185
    /**
1✔
186
     * Delete a specific key in script properties.
1✔
187
     * @param {String} key 
1✔
188
     */
1✔
189
    delete(key) {
1✔
190
        if (this.scriptProperties.getProperty(key) !== null) {
×
191
            this.scriptProperties.deleteProperty(key);
×
192
        }
×
193
    }
×
194
}
1✔
195

1✔
196
/**
1✔
197
 * @classdesc Converts data into JSON for getting/setting in ScriptSettings.
1✔
198
 */
1✔
199
class PropertyData {
1✔
200
    /**
1✔
201
     * 
1✔
202
     * @param {any} propertyData 
1✔
203
     * @param {Number} daysToHold 
1✔
204
     */
1✔
205
    constructor(propertyData, daysToHold) {
1✔
206
        const someDate = new Date();
3✔
207

3✔
208
        /** @property {String} */
3✔
209
        this.myData = JSON.stringify(propertyData);
3✔
210
        /** @property {Date} */
3✔
211
        this.expiry = someDate.setMinutes(someDate.getMinutes() + daysToHold * 1440);
3✔
212
    }
3✔
213

1✔
214
    /**
1✔
215
     * @param {PropertyData} obj 
1✔
216
     * @returns {any}
1✔
217
     */
1✔
218
    static getData(obj) {
1✔
219
        let value = null;
4✔
220
        try {
4✔
221
            value = JSON.parse(obj.myData);
4✔
222
        }
4✔
223
        catch (ex) {
4!
224
            Logger.log(`Invalid property value.  Not JSON: ${ex.toString()}`);
×
225
        }
×
226

4✔
227
        return value;
4✔
228
    }
4✔
229

1✔
230
    /**
1✔
231
     * 
1✔
232
     * @param {PropertyData} obj 
1✔
233
     * @returns {Boolean}
1✔
234
     */
1✔
235
    static isExpired(obj) {
1✔
236
        const someDate = new Date();
4✔
237
        const expiryDate = new Date(obj.expiry);
4✔
238
        return (expiryDate.getTime() < someDate.getTime())
4✔
239
    }
4✔
240
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc