2026-03-20 17:12:09
-ALTER TABLE → modify existing table structure.
-Operations: add/drop column, change datatype, rename column/table, add/drop constraints.
Common ALTER TABLE Operations
1. Add a new column
ALTER TABLE Employee
ADD COLUMN email VARCHAR(100);
2. Drop (remove) a column
ALTER TABLE Employee
DROP COLUMN email;
3. Change datatype of a column
ALTER TABLE Employee
ALTER COLUMN salary TYPE NUMERIC(12,2);
4. Rename a column
ALTER TABLE Employee
RENAME COLUMN name TO full_name;
5. Add a constraint
ALTER TABLE Employee
ADD CONSTRAINT salary_positive CHECK (salary > 0);
6. Drop a constraint
ALTER TABLE Employee
DROP CONSTRAINT salary_positive;
7. Rename the table itself
ALTER TABLE Employee
RENAME TO Staff;
This is the basic PostgreSQL...
See you soon in the next blog!....
2026-03-20 17:11:10
Given an array arr[], your task is to print every alternate element of the array starting from the first element.
In other words, print elements at index 0, 2, 4, ... and skip the rest.
An array of integers arr[]
Print the alternate elements of the array (starting from index 0)
Example 1:
Input: arr[] = [10, 20, 30, 40, 50]
Output: 10 30 50
Explanation:
Print the first element (10), skip the second (20), print the third (30), skip the fourth (40), and print the fifth (50).
Example 2:
Input: arr[] = [-5, 1, 4, 2, 12]
Output: -5 4 12
1 ≤ arr.length ≤ 10^5-10^9 ≤ arr[i] ≤ 10^92026-03-20 17:10:01
To address the data-loss problem (see Part 1), Google introduced Consent Mode.
The goal is to allow analytics tags to load before consent, but with restricted behaviour.
With Advanced Consent Mode:
User visits site
↓
Google tag loads
↓
Consent state = denied
↓
Cookieless pings sent
These pings may contain:
But they do not set cookies.
Google uses these signals to estimate user behaviour.
Basic consent mode: Google Analytics loads only after consent
Advanced consent mode: Google tag loads immediately with restricted tracking
Example cookie consent logic:
import ReactGA from "react-ga4"
export function enableAnalytics() {
const GA_ID = process.env.REACT_APP_GA_ID
if (GA_ID) {
ReactGA.initialize(GA_ID)
}
}
Called when the user clicks Accept.
Advanced Consent Mode uses the gtag consent API.
Default state:
window.gtag("consent", "default", {
analytics_storage: "denied",
ad_storage: "denied"
})
When the user accepts:
window.gtag("consent", "update", {
analytics_storage: "granted",
ad_storage: "granted"
})
When the user declines:
window.gtag("consent", "update", {
analytics_storage: "denied"
})
This allows the Google tag to operate in restricted cookieless mode.
This series is based on real-world work building CSSEXY, a visual UI platform where understanding user behaviour is essential for improving the product.
All articles are also available on CSSEXY and there in the Gallery.
2026-03-20 17:01:49
const x = "dừng tay, chuyển nhánh, pull code, build app"
Nếu anh em đã từng làm React Native một thời gian, chắc chắn không ít lần nghe câu quen thuộc từ QA: "Em ơi build lại cho chị cái task này với." Rồi phải x đợi 20–30 phút, gửi file, rồi lại ngồi chờ phản hồi. Cứ lặp đi lặp lại như vậy mỗi ngày.
Vấn đề x có thể giải quyết bằng việc tích hợp CI/CD để tự động hóa quá trình này. Nhưng vẫn không tránh khỏi việc mất thời gian, QA vẫn phải đợi pipeline build xong mới có app để test. Chưa kể có các task cần khách UAT trên TestFlight thì vẫn không tránh khỏi việc dev cần tự mình build lại app để đẩy lên cho khách UAT.
Đây không phải vấn đề của một người — đây là vấn đề của cả team. Và nó hoàn toàn có thể giải quyết được.
Tài liệu này mô tả hệ thống Dynamic Hot Update Channel — một giải pháp OTA (Over-The-Air) update được thiết lập trong project, cho phép dev deploy thay đổi JavaScript trực tiếp lên thiết bị của QA trong vài giây, không cần build lại native app, không cần qua TestFlight, không cần chờ đợi.
Trước khi đi vào giải pháp, hãy nhìn thẳng vào những điểm bất cập hiện tại:
Mỗi lần có thay đổi JavaScript — dù chỉ là sửa một dòng text hay điều chỉnh màu button — team vẫn phải trải qua toàn bộ quy trình: CI/CD build ra file .ipa hoặc .apk, đợi 15–20 phút, rồi QA mới có app để test -> Lãng phí thời gian không cần thiết.
Flow CI/CD hiện tại chỉ support internal test cho case app IOS. Nên việc khách UAT task thì vẫn cần dev phải tự build app rồi đẩy lên TestFlight để khách có thể test. Case này là gây mất thời gian nhất vì dev phải thực hiện đúng một vòng x để có app cho khách UAT.
Hãy tưởng tượng một ứng dụng React Native như một rạp chiếu phim. Phần native (iOS/Android shell) chính là toàn bộ cơ sở hạ tầng của rạp — tòa nhà, màn hình, hệ thống âm thanh, ghế ngồi. Phần JavaScript bundle chính là bộ phim đang chiếu - nội dung thực sự mà khán giả đến để xem.
Khi muốn chiếu phim mới, bạn không cần phá rạp đi xây lại. Bạn chỉ cần thay cuộn phim. Đó chính xác là cách OTA update hoạt động: thay vì build lại toàn bộ app, chúng ta chỉ đẩy một JavaScript bundle mới lên server. App tự kiểm tra, tải về và chạy — người dùng thấy version mới mà không cần làm gì cả.
Nhưng nếu muốn nâng cấp hệ thống âm thanh Dolby hay lắp thêm máy chiếu 4K — lúc đó mới cần đụng đến phần xây dựng vật lý. Tương tự, khi app cần thêm native module, thay đổi cấu hình iOS/Android, hay cập nhật dependency có native code — bạn vẫn phải build lại native app như bình thường.
Giới hạn quan trọng: OTA update chỉ áp dụng được cho thay đổi ở tầng JavaScript và Assets (hình ảnh, font). Nếu thêm native module mới, thay đổi cấu hình iOS/Android, hay cập nhật dependency có native code — vẫn phải build lại native app như bình thường.
Thay vì có một staging environment duy nhất cho tất cả, mình sẽ tạo ra các channel riêng biệt — mỗi channel là một "làn đường" độc lập chứa JavaScript bundle của một task cụ thể.
Ví dụ thực tế:
stg → chứa code của nhánh develop- nhánh ổn định nhất và sẽ là nhánh được chọn để build Native App. V567APP-199 → chứa code JS của task V567APP-199 rẽ từ develop, QA muốn test task này thì chỉ cần switch sang "làn đường" - Channel V567APP-199 là có code mới nhất của task để test.QA có thể switch qua lại giữa các channel ngay trên một app đã cài sẵn, không cần cài lại, không cần bản build mới.
Hệ thống được xây dựng trên hot-updater với Cloudflare làm backend, được chọn vì chi phí thấp, tốc độ cao phù hợp với mục đích phục vụ cho internal test hiện tại.
┌─────────────────────────────────────────────────────────┐
│ Cloudflare Infrastructure │
│ │
│ ┌──────────────────┐ ┌──────────────────────────┐ │
│ │ Cloudflare R2 │ │ Cloudflare D1 │ │
│ │ (Object Storage)│ │ (SQLite Database) │ │
│ │ │ │ │ │
│ │ • bundle.js │ │ channel | version | ... │ │
│ │ • assets/ │ │ stg | 4.0.0 | ... │ │
│ │ • fonts/ │ │ V567-99 | 4.0.0 | ... │ │
│ └──────────────────┘ └──────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
▲ ▲
│ deploy │ query metadata
│ │
┌────────┴──────────┐ ┌──────────┴──────────────┐
│ Developer CLI │ │ React Native App │
│ yarn hot:task: │ │ (on QA's device) │
│ ios V567APP-199 │ │ → checks channel, │
└───────────────────┘ │ → downloads bundle │
│ → reloads JS engine │
└─────────────────────────┘
Các thành phần chính:
| Thành phần | Vai trò |
|---|---|
| Cloudflare R2 | Lưu trữ JavaScript bundle và static assets |
| Cloudflare D1 | Database theo dõi metadata: channel nào đang chạy bundle nào, version nào |
hot-updater CLI |
Tool deploy bundle từ máy developer lên Cloudflare |
DevChannelSwitcher |
UI trong app để QA switch channel |
Các channel được định nghĩa:
stg — Channel mặc định, luôn phản ánh code của nhánh staging chính (develop). Logic hiện tại khi build native app với config staging sẽ auto set default channel là stg
[Task-Name] — Ephemeral channel (tồn tại tạm thời) cho từng Jira ticket, ví dụ V567APP-199
Việc set-up, cấu hình anh em có thể check trực tiếp trên docs của Hot Updater nhé 🫡: https://hot-updater.dev/docs/get-started/introduction
Các script được định nghĩa trong package.json, sử dụng biến môi trường từ .env.stg:
"hot:console": "npx hot-updater console",
"hot:stg:ios": "ENVFILE=.env.stg npx hot-updater deploy -p ios -t \"4.0.0\" --channel stg",
"hot:stg:android": "ENVFILE=.env.stg npx hot-updater deploy -p android -t \"4.0.0\" --channel stg",
"hot:stg": "yarn hot:stg:ios && yarn hot:stg:android",
"hot:task:ios": "ENVFILE=.env.stg npx hot-updater deploy -p ios -t \"4.0.0\" --channel",
"hot:task:android": "ENVFILE=.env.stg npx hot-updater deploy -p android -t \"4.0.0\" --channel"
Mổ xẻ từng phần của một lệnh deploy điển hình:
ENVFILE=.env.stg npx hot-updater deploy -p ios -t "4.0.0" --channel stg
│ │ │ │ │
│ │ │ │ └─ Tên channel sẽ nhận bundle này
│ │ │ └─ Version targeting: chỉ apply cho app 4.0.0
│ │ └─ Platform: ios hoặc android
│ └─ Lệnh deploy của hot-updater CLI
└─ Inject .env.stg làm môi trường build (API keys, app config, v.v.)
Tại sao cần ENVFILE=.env.stg?
Khi JavaScript bundle được tạo ra, nó sẽ bake-in các giá trị từ file .env vào trong bundle — bao gồm API endpoint, feature flags, v.v. Chỉ định .env.stg đảm bảo bundle được build với đúng config của môi trường staging, không phải production.
DEVELOPMENT_MODE=true để check điều kiện chỉ implement với các bản build app ở staging không gây rủi do đến các bản build của production.
const isDevelopmentMode = Boolean(Config.DEVELOPMENT_MODE === "true")
export default isDevelopmentMode
? HotUpdater.wrap({
baseURL: "[https://hot-update.staging.workers.dev/api/check-update](https://hot-update.staging.workers.dev/api/check-update)",
updateStrategy: "appVersion",
updateMode: "auto",
})(App)
: App
Tại sao cần -t "4.0.0"?
hot-updater.config.ts đang dùng updateStrategy: "appVersion" — nghĩa là Cloudflare D1 sẽ lưu thêm thông tin version targeting cho mỗi bundle. Khi app khởi động và check update, nó gửi kèm app version hiện tại. Cloudflare D1 chỉ trả về bundle nếu version của thiết bị thỏa điều kiện 4.0.0 ( Version chung của các bản build staging )
Sự khác biệt giữa hot:stg và hot:task:
hot:stg deploy lên channel cố định stg — channel này luôn tồn tại và là nơi toàn bộ team nhận update sau mỗi lần merge.
hot:task:ios và hot:task:android không có --channel cố định — dev phải tự append tên channel khi gọi:
yarn hot:task:ios V567APP-199
# tương đương với:
# ENVFILE=.env.stg npx hot-updater deploy -p ios -t "4.0.0" --channel V567APP-199
DevChannelSwitcher.tsx — Một FAB (Floating Action Button) được inject vào root của app, chỉ hiển thị khi DEVELOPMENT_MODE=true (tức là chỉ có trên bản staging, không bao giờ xuất hiện trên production).
UI này cung cấp một modal với 3 chức năng:
stg mặc địnhNOTE: Do logic của hot update, bắt buộc cần phải chuyển về default channel rồi sau đó mới có thể chuyển đến các Ephemeral channel khác. Ví dụ từ default channel
stgchuyển sang channelV567APP-199để test. Test xong, muốn sang taskV567APP-200thì cần Reset vềstgsau đó chuyển sang taskV567APP-200không được chuyển trực tiếp từV567APP-199quaV567APP-200.
![]() |
![]() |
|---|---|
| 1. App ở default channel | 2. App ở Ephemeral channel |
Khi hoàn thành task V567APP-199 và muốn QA bắt đầu test:
Bước 1: Đảm bảo code đã sẵn sàng trên branch local.
Bước 2: Chạy lệnh deploy lên channel riêng của task:
yarn hot:task:ios V567APP-199
yarn hot:task:android V567APP-199
Lệnh này sẽ bundle JavaScript hiện tại ở local và push lên Cloudflare dưới channel V567APP-199. Toàn bộ quá trình mất khoảng 1–2 phút, thay vì 20 phút build native.
Bước 3: Ping QA trên Jira/Mattermost: "Task V567APP-199 Không có phần update native code và đã được deploy lên rồi"
Lưu ý: Mỗi lần chạy lại lệnh deploy cho cùng một channel, bundle cũ sẽ bị ghi đè. QA nếu đang ở channel đó rồi thì chỉ cần đợi app tải về bundle vừa được deploy lên là có code mới nhất vừa được dev update để test, không cần làm gì thêm. 😳
Bước 1: Mở Staging App trên thiết bị.
Bước 2: Tap vào icon 🔥 ở góc dưới bên phải màn hình để mở Task Switcher.
Bước 3: Nhập tên task vào ô input: V567APP-199
Bước 4: Tap "🚀 Switch task".
App sẽ tự động tải JavaScript bundle của task đó từ Cloudflare và reload. Sau khi reload xong, app sẽ chạy đúng code của V567APP-199.
Bước 5 (khi test xong): Mở lại Task Switcher → tap "🔄 Reset" để quay về channel stg mặc định.
NOTE: Có thể switch qua lại giữa nhiều task trong cùng một buổi test mà không cần cài lại app hay làm gì phức tạp. Chỉ cần nhập đúng tên channel và tap Switch.
Khi PR của V567APP-199 đã được approve và merge vào nhánh staging chính, cần cập nhật channel stg để toàn bộ team nhận được code mới:
Bước 1: Pull code staging mới nhất về máy.
Bước 2: Deploy lên channel stg:
yarn hot:stg
Script này sẽ deploy song song cho cả iOS và Android. Từ lần mở app tiếp theo, mọi thiết bị đang dùng channel stg sẽ tự động fetch và apply bundle mới.
Quan trọng: Chỉ chạy
yarn hot:stgsau khi PR đã được merge và code trên nhánh staging đã ổn định. Tránh deploy code đang dở dang lên channelstgvì nó sẽ ảnh hưởng đến toàn bộ team.
Để xem toàn bộ lịch sử deployment, kiểm tra bundle nào đang active trên channel nào, hoặc thực hiện rollback khi cần:
yarn hot:console
Lệnh này mở một local web interface kết nối trực tiếp với Cloudflare D1 và R2, cho phé:
| Quy trình cũ | Với Hot Update Channel | |
|---|---|---|
| Thời gian deploy cho QA | 10–15 phút đợi tool (build native), 20-30 phút cho đẩy TestFlight UAT | 1–2 phút |
| Khi update code ở task | Cần build lại app | Không cần làm gì, app tự tải code mới nhất về |
| Test song song nhiều task | Cần nhiều bản build riêng | Switch channel ngay trên một app |
| Rollback khi có bug | Build lại từ commit cũ | Rollback qua dashboard, tức thì |
| Áp dụng cho | Mọi thay đổi | Chỉ JS/Assets, không áp dụng cho native changes |
AndroidManifest, cập nhật native module), dev vẫn phải build native app bình thường. Hot update chỉ dành cho JS/Assets.4.0.0 (default version của staging). Thiết bị đang chạy app version khác sẽ không nhận update..env.hotupdater là file nhạy cảm: Không commit file này lên git. Nếu cần onboard thành viên mới, chia sẻ qua kênh bảo mật riêng.2026-03-20 17:01:03
Filtering (WHERE clause)
Filtering means → only certain rows are selected based on a condition.
SELECT name, salary
FROM Employee
WHERE salary > 55000;
Output:
| name | salary |
|---|---|
| Priya | 60000 |
| Meena | 58000 |
| Arun | 62000 |
Sorting (ORDER BY clause)
Sorting means → arrange rows in ascending (ASC) or descending (DESC) order.
SELECT name, salary
FROM Employee
ORDER BY salary DESC;
See you soon in the next topic!.......