Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions database/ignite/ignite.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
const IgniteClient = require("apache-ignite-client");
const logger = require("./../../lib/log")(__filename);
const { SqlFieldsQuery } = require("apache-ignite-client/lib/Query");
const IgniteClientConfiguration = IgniteClient.IgniteClientConfiguration;
const CacheConfiguration = IgniteClient.CacheConfiguration;
require("dotenv").config();
let igniteModule = {};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

igniteModule should be declared const instead of let

let igniteClient;
igniteModule.startIgnite = async () => {
try {
igniteClient = new IgniteClient();
const configuration = new IgniteClientConfiguration(process.env.IGNITE_HOST)
.setUserName(process.env.IGNITE_USER)
.setPassword(process.env.IGNITE_PASSWORD);
await igniteClient.connect(configuration);
logger.info("Connected to Ignite database");
} catch (err) {
logger.error(err);
throw new Error(err);
}
};
igniteModule.checkAccount = async (username) => {
if (!username) return;
try {
const caches = await igniteClient.cacheNames();
return caches.includes(username);
} catch (err) {
logger.error(`Error checking ignite database for ${username}`);
throw new Error(err);
}
};
igniteModule.closeIgnite = async () => {
try {
await igniteClient.disconnect();
logger.info("Closed connection to Ignite database");
} catch (err) {
logger.error(`Error closing connection to Ignite database`);
throw new Error(err);
}
};

igniteModule.createAccount = async (user) => {
const { username, dbPassword } = user;
if (!username || !dbPassword) return;
try {
const cache = await igniteClient.getOrCreateCache(
username,
new CacheConfiguration().setSqlSchema("PUBLIC")
);
await cache.query(
new SqlFieldsQuery(
`CREATE USER "${username}" WITH PASSWORD '${dbPassword}'`
)
);
logger.info(`Successfully created ${username} cache`);
} catch (err) {
logger.error(`Error creating ignite ${username}`);
throw new Error(err);
}
};
igniteModule.deleteAccount = async (username) => {
if (!username) return;
try {
const cache = await igniteClient.getOrCreateCache(
username,
new CacheConfiguration().setSqlSchema("PUBLIC")
);
await cache.query(new SqlFieldsQuery(`DROP USER "${username}"`));
await igniteClient.destroyCache(username);
logger.info(`Successfully deleted ${username} cache`);
} catch (err) {
logger.error(`Error deleting ignite ${username}`);
throw new Error(err);
}
};
module.exports = igniteModule;
110 changes: 110 additions & 0 deletions database/ignite/ignite.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
jest.mock("apache-ignite-client");
jest.mock("../../lib/log");
const logGen = require("../../lib/log");
const logger = {
info: jest.fn(),
error: jest.fn(),
};
logGen.mockReturnValue(logger);
require("dotenv").config();
const {
startIgnite,
createAccount,
deleteAccount,
checkAccount,
closeIgnite,
} = require("./ignite");

const IgniteClient = require("apache-ignite-client");
const mockClient = {
cacheNames: jest.fn(),
connect: jest.fn(),
disconnect: jest.fn(),
getOrCreateCache: jest.fn().mockReturnThis(),
query: jest.fn(),
destroyCache: jest.fn(),
};
IgniteClient.mockImplementation(() => mockClient);
const mockConfig = {
setUserName: jest.fn().mockReturnThis(),
setPassword: jest.fn().mockReturnThis(),
};
IgniteClient.IgniteClientConfiguration.mockImplementation(() => mockConfig);
describe("IgniteDb functions", () => {
beforeEach(() => {
jest.clearAllMocks();
});
const user = { username: "username", dbPassword: "password" };
it("should start Ignite client", () => {
startIgnite();
expect(IgniteClient).toHaveBeenCalledTimes(1);
expect(mockClient.connect).toHaveBeenCalledTimes(1);
expect(IgniteClient.IgniteClientConfiguration).toHaveBeenCalledTimes(1);
expect(mockConfig.setUserName.mock.calls[0][0]).toEqual("ignite");
expect(mockConfig.setPassword.mock.calls[0][0]).toEqual("ignite");
});
it("should throw an error if starting client goes wrong", async () => {
try {
mockClient.connect.mockReturnValue(Promise.reject());
expect(await startIgnite()).rejects.toThrow();
} catch (err) {
expect(logger.error).toHaveBeenCalledTimes(1);
}
});
it("should close ignite client", () => {
closeIgnite();
expect(mockClient.disconnect).toHaveBeenCalledTimes(1);
});
it("should call logger error if closing client goes wrong", async () => {
try {
mockClient.disconnect.mockReturnValue(Promise.reject());
await closeIgnite();
} catch (err) {
expect(logger.error).toHaveBeenCalledTimes(1);
}
});
it("should check for existsting accounts", async () => {
mockClient.cacheNames.mockReturnValue(["testName", "", "username"]);
const result = await checkAccount(user.username);
expect(mockClient.cacheNames).toHaveBeenCalledTimes(1);
expect(result).toEqual(true);
});
it("should call logger error if checking for account goes wrong", async () => {
try {
mockClient.cacheNames.mockReturnValue(Promise.reject());
await checkAccount(user.username);
} catch (err) {
expect(logger.error).toHaveBeenCalledTimes(1);
}
});
it("should create new account", async () => {
await createAccount(user);
expect(mockClient.getOrCreateCache.mock.calls[0][0]).toEqual(user.username);
});
it("should delete user", async () => {
await deleteAccount(user.username);
expect(mockClient.getOrCreateCache.mock.calls[0][0]).toEqual(user.username);
expect(mockClient.destroyCache.mock.calls[0][0]).toEqual(user.username);
});
it("should call logger error if creating a user goes wrong", async () => {
try {
mockClient.getOrCreateCache.mockReturnValue(Promise.reject());
expect(await createAccount(user)).rejects.toThrow();
} catch (err) {
expect(logger.error).toHaveBeenCalledTimes(1);
}
});
it("should return undefined if no arguments were provided", async () => {
expect(await createAccount({})).toEqual(undefined);
expect(await checkAccount("")).toEqual(undefined);
expect(await deleteAccount("")).toEqual(undefined);
});
it("should calll logger error if deleting user goes wrong", async () => {
try {
mockClient.getOrCreateCache.mockReturnValue(Promise.reject());
await deleteAccount(user.username);
} catch (err) {
expect(logger.error).toHaveBeenCalledTimes(1);
}
});
});
22 changes: 22 additions & 0 deletions database/ignite/igniteConfig.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.apache.ignite.configuration.IgniteConfiguration">
<property name="dataStorageConfiguration">
<bean class="org.apache.ignite.configuration.DataStorageConfiguration">
<property name="defaultDataRegionConfiguration">
<bean class="org.apache.ignite.configuration.DataRegionConfiguration">
<property name="maxSize" value="#{200 * 1024 * 1024}"/>
<property name="persistenceEnabled" value="true"/>
</bean>
</property>
</bean>
</property>

<property name="authenticationEnabled" value="true"/>

</bean>
</beans>
4 changes: 4 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const db = require("../sequelize/db");
const pg = require("../database/postgres/pg");
const arango = require("../database/arango/arango");
const es = require("../database/elasticsearch/elastic");
const ignite = require("../database/ignite/ignite");
const logger = require("./log")(__filename);

const util = {};
Expand Down Expand Up @@ -37,6 +38,9 @@ util.cleanAnonymous = async () => {
const arangoDbExists = await arango.checkIfDatabaseExists(username);
if (arangoDbExists) await arango.deleteAccount(username);

const igniteDbExists = await ignite.checkAccount(username);
if(igniteDbExists) await ignite.deleteAccount(username);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a space after the if? For codebase styling consistency. We have a prettierrc.js file that is supposed to take care of styling. Are you using prettier?

https://github.com/garageScript/databases/blob/master/.prettierrc.js

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use prettier as default vscode formatter usually, so I forgot to check these files before submitting. I run prettier over all database files in new commit and it picked up a few inconsistencies here and there, mostly empty spaces around objects.
Everything should be fixed now.


return await user.destroy();
})
).then(() => {
Expand Down
6 changes: 6 additions & 0 deletions lib/util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ jest.mock("../sequelize/db");
jest.mock("../database/postgres/pg");
jest.mock("../database/arango/arango");
jest.mock("../database/elasticsearch/elastic");
jest.mock("../database/ignite/ignite");
jest.mock("./log");

const sequelize = require("sequelize");
const db = require("../sequelize/db");
const pg = require("../database/postgres/pg");
const es = require("../database/elasticsearch/elastic");
const arango = require("../database/arango/arango");
const ignite = require("../database/ignite/ignite");

sequelize.Op = { and: "and", lt: "lt" };
const Accounts = {
Expand All @@ -21,6 +23,7 @@ db.getModels = () => {
pg.deletePgAccount = jest.fn();
es.deleteAccount = jest.fn();
arango.deleteAccount = jest.fn();
ignite.deleteAccount = jest.fn();
const logGen = require("./log");
const logger = {
info: jest.fn(),
Expand Down Expand Up @@ -58,16 +61,19 @@ describe("Testing cleanAnonymous function", () => {
expect(pg.deletePgAccount).not.toHaveBeenCalled();
expect(es.deleteAccount).not.toHaveBeenCalled();
expect(arango.deleteAccount).not.toHaveBeenCalled();
expect(ignite.deleteAccount).not.toHaveBeenCalled();
});
it("should call database functions if expired accounts are found", async () => {
Accounts.findAll.mockReturnValue([{ destroy: () => {} }]);
pg.userHasPgAccount = () => true;
es.checkAccount = () => true;
arango.checkIfDatabaseExists = () => true;
ignite.checkAccount = ()=>true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you change the format to include spaces?

ignite.checkAccount = () => true

await util.cleanAnonymous();
expect(pg.deletePgAccount).toHaveBeenCalled();
expect(es.deleteAccount).toHaveBeenCalled();
expect(arango.deleteAccount).toHaveBeenCalled();
expect(ignite.deleteAccount).toHaveBeenCalled();
});
it("should call logger.error if cleaning fails", async () => {
Accounts.findAll.mockImplementation(() => {
Expand Down
19 changes: 19 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
},
"dependencies": {
"@types/node-fetch": "^2.5.7",
"apache-ignite-client": "^1.0.0",
"arangojs": "^7.0.2",
"base-x": "^3.0.8",
"bcrypt": "^5.0.0",
Expand Down
59 changes: 59 additions & 0 deletions public/lessons/Ignite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

# Intro

In-memory database is a database which relies primarily on memory for data storage.

Pros:
* Performance. RAM is way faster than any disks IOs

Cons:
* Data persistence can be tricky

Some datasets could be stored entirely in memory but most often in-memory databases are used as caches.

# Apache Ignite

Ignite is an open source distributed in-memory database. It can be used as cache, data grid or key-value store. In our case we will use the latter.

Key-value store is the simplest data model, one unique key which is mapped to some value. With no schema limitations writes are very fast, but since database doesn't know anything about the value you can't use indexes to speed up searches.

# Client

Ignite provides their own node.js client [apache-ignite-client](https://www.npmjs.com/package/apache-ignite-client).

To connect to database:
```
const IgniteClient = require("apache-ignite-client");
const IgniteClientConfiguration = IgniteClient.IgniteClientConfiguration;

const igniteClientConfiguration = new IgniteClientConfiguration("host")
.setUserName("username")
.setPassword("password");
const connect = async () => {
const igniteClient = new IgniteClient();
await igniteClient.connect(igniteClientConfiguration);
};
connect();
```

To add values into cache you need to connect to cache, specify data type and its key:

```
const ObjectType = IgniteClient.ObjectType;
const basicOperations = async () => {
const igniteClient = new IgniteClient();
await igniteClient.connect(igniteClientConfiguration);
console.log("successfully connected");
const cache = (await igniteClient.getOrCreateCache("username")).setKeyType(
ObjectType.PRIMITIVE_TYPE.INTEGER
);
await cache.put(1, "hello world");
console.log(await cache.get(1));
igniteClient.disconnect();
};
basicOperations();
```

You can find more examples [here](https://github.com/apache/ignite/tree/master/modules/platforms/nodejs/examples).

*Under construction*
Loading